├── .gitignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── api ├── api.js ├── logging.js ├── package.json ├── query.js └── yarn.lock ├── backend ├── app │ ├── controllers │ │ ├── eventhistory.controller.js │ │ ├── listprovider.controller.js │ │ ├── provider.controller.js │ │ ├── servicelist.controller.js │ │ └── user.controller.js │ ├── models │ │ ├── db.js │ │ ├── eventhistory.model.js │ │ ├── listprovider.model.js │ │ ├── provider.model.js │ │ ├── servicelist.model.js │ │ └── user.model.js │ └── routes │ │ ├── eventhistory.routes.js │ │ ├── listprovider.routes.js │ │ ├── provider.routes.js │ │ ├── public.js │ │ ├── servicelist.routes.js │ │ └── user.routes.js ├── logging.js ├── middleware │ └── authentication.js ├── package.json ├── server.js └── yarn.lock ├── common ├── .eslintrc.js ├── countries.js ├── dev_constants.js └── languages.js ├── db.sql ├── docs ├── dvb-csr └── index.html ├── frontend ├── .env ├── babel.config.js ├── index.html ├── package.json ├── public │ └── favicon.ico ├── src │ ├── App.vue │ ├── assets │ │ ├── logo.png │ │ ├── logo_dvb-i.png │ │ └── logo_sofiadigital.png │ ├── components │ │ ├── Login │ │ │ ├── Login.vue │ │ │ ├── Logout.vue │ │ │ └── Setup.vue │ │ ├── User │ │ │ └── Profile.vue │ │ ├── admin │ │ │ ├── AddUser.vue │ │ │ ├── AdminView.vue │ │ │ └── EditUser.vue │ │ ├── providers │ │ │ ├── Provider.vue │ │ │ ├── ProviderList.vue │ │ │ └── ProviderView.vue │ │ ├── servicelist │ │ │ ├── ServiceList.vue │ │ │ └── ServiceListList.vue │ │ └── settings │ │ │ └── Settings.vue │ ├── countries.js │ ├── dev_constants.js │ ├── http-common.js │ ├── languages.js │ ├── main.js │ ├── router.js │ └── services │ │ ├── ListProviderDataService.js │ │ ├── LoginService.js │ │ ├── ProviderDataService.js │ │ ├── ServiceListDataService.js │ │ └── UserDataService.js ├── vite.config.js └── yarn.lock ├── utils └── genreparser │ ├── genreparser.js │ ├── package.json │ └── yarn.lock └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | **/dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | #API .env file 11 | api/.env 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | pnpm-debug.log* 18 | 19 | # Editor directories and files 20 | .idea 21 | .vscode 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | 28 | db.config.js 29 | .env -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'always', 3 | bracketSpacing: true, 4 | endOfLine: 'lf', 5 | htmlWhitespaceSensitivity: 'strict', 6 | printWidth: 120, 7 | proseWrap: 'never', 8 | quoteProps: 'as-needed', 9 | semi: true, 10 | singleQuote: false, 11 | tabWidth: 2, 12 | trailingComma: 'es5', 13 | useTabs: false, 14 | vueIndentScriptAndStyle: true, 15 | }; 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sofia Digital 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DVB-I-Reference-CSR 2 | 3 | DVB-I clients, which could be anything from smartphones, tablets and laptops to TV sets, set-top boxes and USB sticks, require a means to find one or more service lists. This is referred to as service list discovery. The client may have one or more built-in or privately provisioned service list URLs or it may make use of one or more service list registries. These may be operated by, or on behalf of, various kinds of organizations such as the manufacturer of the device, a national or regional regulator, or an operator or platform brand serving only their own clients. 4 | 5 | The specification also allows for a Central Service List Registry (CSR), operated for the benefit of all devices implementing the DVB-I client, providing information on a wide set of service lists known to that registry. A CSR could potentially play a role in kickstarting a horizontal market for DVB-I. The creation of a skeleton CSR is intended to provide the DVB Project with a means of verifying how a fully functional CSR would perform under conditions defined within the RfP. 6 | 7 | ## Technical details 8 | 9 | The project consists of 3 modules, frontend, backend and api. 10 | All modules use yarn. To initialize the modules, yarn in each module directory after cloning the repository 11 | 12 | Frontend is the WEB UI for adding and editing service lists and providers. 13 | 14 | Backend is used by the frontend to access the CSR database. 15 | 16 | API is the query API used by the DVB-I clients to access the CSR. 17 | 18 | SQL database is required. Database schema is found in db.sql file in the project root. Initialize the database using this sql file. The DB-schema file includes 19 | alter table commands to update the schema from a previous version of the CSR application. These alter table commands will generate errors when creating a new database, these can be ignored. For this reason the db.sql initialization should be run with --force or equivalent option to continue even if there are errors thrown. 20 | 21 | ## API and backend module configuration 22 | 23 | Database and port configuration uses .env files. 24 | Database defaults are: 25 | 26 | * database host: "localhost", 27 | * database port: "3306", 28 | * database user: "user", 29 | * database password: "password", 30 | * database name: "dvb_i_csr" 31 | 32 | backend default port is 3000 and API default port is 3001. 33 | 34 | To configure the values create an .env file in the api/ and backend/-directory containing the values: 35 | ``` 36 | DB_HOST= 37 | DB_PORT= 38 | DB_USER= 39 | DB_PASSWORD= 40 | DB_NAME= 41 | PORT= 42 | ``` 43 | Example .env file contents defining the DB host, user and password: 44 | ``` 45 | DB_HOST=localhost 46 | DB_USER=dbuser 47 | DB_PASSWORD=dbpassword 48 | ``` 49 | 50 | If a value is not defined in the .env-file, the default value is used. Port and database name can usually use the default values but they can be configured if needed. 51 | ``` 52 | DB_HOST=localhost 53 | DB_USER=dbuser 54 | DB_PASSWORD=dbpassword 55 | DB_NAME=dvb_i_csr-1 56 | PORT=3333 57 | ``` 58 | Create the .env files to "api" and "backend" directory, respectively. 59 | 60 | Backend module requires a random string for json web token secret in the .env-file, minimum length should be 32 bytes: 61 | ``` 62 | JWT_SECRET= 63 | ``` 64 | 65 | ## API Module usage 66 | 67 | After cloning the repository, install the required node modules with the command `yarn install` in the api-directory. 68 | Start the api server with the command `node api.js` in the api-directory. Default port is 3001 69 | 70 | API module uses a connection pool for sql connections. By default it uses 10 connections. The amount of connections can be configured in the .env file with the variable `DB_CONNECTIONS`. For example, to use a maximum of 50 connections, enter the following line to the .env file: 71 | ``` 72 | DB_CONNECTIONS=50 73 | ``` 74 | 75 | ### Redis cache 76 | 77 | The API can use redis in-memory-database to cache the responses for better performance. 78 | To enable the redis caching, enter the following line to the .env-file: 79 | ``` 80 | REDIS_ENABLED=true 81 | ``` 82 | To configure redis host, port and password, you can use the following env variables in the api/.env-file: 83 | ``` 84 | REDIS_HOST=localhost 85 | REDIS_PORT=34542 86 | REDIS_PASSWORD=redispassword 87 | ``` 88 | 89 | Cached responses expire after 5 minutes. Expiry time can be configured with the following variable: 90 | ``` 91 | REDIS_EXPIRES=120 92 | ``` 93 | Expiry value is in seconds. 94 | 95 | Default values are: 96 | * Host: localhost 97 | * Port: 6379 98 | * no password 99 | * cached response expires after 5 minutes 100 | 101 | ## Backend Module usage 102 | 103 | After cloning the repository, install the required node modules with the command `yarn install` in the backend-directory. 104 | Start the backend server with the command `node server.js` in the backend-directory. Default port is 3000 105 | 106 | ## Frontend Module usage 107 | 108 | After cloning the repository, install the required node modules with the command `yarn install` in the frontend-directory. 109 | Start the frontend development server with the command `yarn serve` in the frontend-directory. Default port is 8080. If 8080 is reseverved, 110 | it will use the next free port. The yarn serve command will tell what port it is using. 111 | To build production version of the frontend, use the command `yarn build` in the frontend-directory. The production codes can be found 112 | in the frontend/dist-directory. 113 | 114 | #### Known issues 115 | Node.js version 17 and greater uses OpenSSL 3 and the frontend requires OpenSSL 2. In that case starting the frontend will fail with an error: 116 | ``` 117 | code: 'ERR_OSSL_EVP_UNSUPPORTED' 118 | ``` 119 | To fix this issue, a workaround is required. Before `yarn serve` or `yarn build` command an environment variable needs to be set using a command: 120 | 121 | Linux: `export NODE_OPTIONS=--openssl-legacy-provider` 122 | 123 | Windows: `set NODE_OPTIONS=--openssl-legacy-provider` 124 | 125 | ## Test server and Query API 126 | 127 | A test server Frontend is running at https://csr.dtv.fi/ 128 | Please apply for an account from [juha.joki@sofiadigital.com ](mailto:juha.joki@sofiadigital.com) 129 | All newly created users will have empty provider and service list instances, they can create new ones as one pleases. Please note that at this point the test server DB should not be counted to be permament - i.e. it can be reset at some point in time. 130 | 131 | The Test Server Query API can be accessed from https://csr.dtv.fi/api/query 132 | Example queries: 133 | https://csr.dtv.fi/api/query?TargetCountry=FIN 134 | https://csr.dtv.fi/api/query?ProviderName=Servicelist%20provider%202 135 | 136 | Cache can be disabled using the endpoint https://csr.dtv.fi/api/query-nocache. 137 | 138 | Refer to https://dvb.org/wp-content/uploads/2020/11/A177r2_Service-Discovery-and-Programme-Metadata-for-DVB-I_ts_103-770-v120_June-2021.pdf chapter 5.1.3 and annex C.4. 139 | 140 | ## Issues 141 | 142 | If you have any issue, please report them at https://github.com/DVBProject/DVB-I-Reference-CSR/issues 143 | 144 | 145 | -------------------------------------------------------------------------------- /api/api.js: -------------------------------------------------------------------------------- 1 | const http = require("http"); 2 | const url = require("url"); 3 | const qs = require("qs"); 4 | const md5 = require("md5"); 5 | const csrquery = require("./query.js").csrquery; 6 | require('dotenv').config() 7 | const redis = require("redis"); 8 | const mysql = require("mysql2"); 9 | 10 | const PORT = process.env.PORT || 3001; 11 | const { error, info } = require("./logging"); 12 | 13 | process.on("uncaughtException", (err) => { 14 | error(err) 15 | }); 16 | 17 | let redisClient = null; 18 | 19 | if (process.env.REDIS_ENABLED === "true") { 20 | let config = {}; 21 | if (process.env.REDIS_HOST) { 22 | config.host = process.env.REDIS_HOST; 23 | } 24 | if (process.env.REDIS_PORT) { 25 | config.port = process.env.REDIS_PORT; 26 | } 27 | if (process.env.REDIS_PASSWORD) { 28 | config.password = process.env.REDIS_PASSWORD; 29 | } 30 | redisClient = redis.createClient(config); 31 | info("redis cache initialized"); 32 | } 33 | 34 | const mysqlClient = mysql.createPool({ 35 | connectionLimit: process.env.DB_CONNECTIONS || 10, 36 | host: process.env.DB_HOST || "localhost", 37 | port: process.env.DB_PORT || "", 38 | user: process.env.DB_USER || "user", 39 | password: process.env.DB_PASSWORD || "password", 40 | database: process.env.DB_NAME || "dvb_i_csr", 41 | timezone: "Z", 42 | }); 43 | info("DB connection pool initialized"); 44 | 45 | 46 | csrquery.init(mysqlClient); 47 | http 48 | .createServer(async function (req, res) { 49 | res.setHeader("Access-Control-Allow-Origin", "*"); 50 | res.setHeader("Access-Control-Allow-Headers", "Content-Type"); 51 | if (req.method === "OPTIONS") { 52 | res.writeHead(200); 53 | res.end(); 54 | return; 55 | } 56 | if (req.url === "/favicon.ico") { 57 | res.writeHead(200, { "Content-Type": "image/x-icon" }); 58 | res.end(); 59 | return; 60 | } 61 | 62 | var request = url.parse(req.url, true); 63 | 64 | if (request.pathname) { 65 | if (request.pathname.endsWith("/query")|| request.pathname.endsWith("/query-nocache")) { 66 | try { 67 | const useCache = request.pathname.endsWith("/query-nocache") 68 | const params = qs.parse(request.query); 69 | const keys = Object.keys(params); 70 | keys.sort((a, b) => { 71 | return a.localeCompare(b, 'en', { sensitivity: 'base' }); 72 | }); 73 | //TODO Better hash function or some other way to identify the request? 74 | //Could we use the unhashed json as the key? or gzip the json so 75 | //we could check the parameters from the key 76 | const hash = md5(JSON.stringify(keys) + csrquery.A177r6); 77 | let xml = null; 78 | if (redisClient && useCache) { 79 | xml = await getCachedResponse(hash); 80 | } 81 | 82 | if (!xml) { 83 | xml = await csrquery.generateXML(params, csrquery.A177r6); 84 | if (redisClient && useCache) { 85 | redisClient.set(hash, xml); 86 | redisClient.expire(hash, parseInt(process.env.REDIS_EXPIRES) || 300); //Default expiry, 5 minutes 87 | } 88 | } 89 | if(xml.lastModified) { 90 | res.appendHeader('Last-Modified',xml.lastModified.toUTCString() ) 91 | } 92 | res.writeHead(200, { "Content-Type": "application/xml" }); 93 | res.write(xml.xml); 94 | res.end(); 95 | info('"' + req.url + '"', '"' + req.headers["user-agent"] + '"'); 96 | return; 97 | } catch (e) { 98 | res.writeHead(400); 99 | res.end(); 100 | info(e); 101 | return; 102 | } 103 | } 104 | } 105 | else { 106 | res.writeHead(400); 107 | res.end(); 108 | info("ERROR: Wrong pathname:" + req.pathname); 109 | } 110 | return; 111 | }) 112 | .listen(PORT, () => { 113 | info("API server is running on port " + PORT); 114 | }); 115 | 116 | 117 | async function getCachedResponse(hash) { 118 | try { 119 | return new Promise((resv, rej) => { 120 | redisClient.get(hash, (err, reply) => { 121 | resv(reply); 122 | }); 123 | }); 124 | } catch (e) { 125 | info(e); 126 | return false; 127 | } 128 | }; -------------------------------------------------------------------------------- /api/logging.js: -------------------------------------------------------------------------------- 1 | let logger = null; 2 | 3 | const ERROR = "error" 4 | const LOG = "log" 5 | const WARN = "warn" 6 | const INFO = "info" 7 | 8 | const logs = { 9 | [LOG]: console.log, 10 | [WARN]: console.warn, 11 | [INFO]: console.info, 12 | [ERROR]: console.error 13 | } 14 | 15 | 16 | function log(...val) { 17 | logging(LOG,...val) 18 | } 19 | function warn(...val) { 20 | logging(WARN,...val) 21 | } 22 | function info(...val) { 23 | logging(INFO,...val) 24 | } 25 | function error(...val) { 26 | logging(ERROR,...val) 27 | } 28 | 29 | function logging(type,...val) { 30 | try { 31 | if(logger) { 32 | //Use different logging system, graylog for example 33 | } 34 | else { 35 | logs[type].call(this,...val) 36 | } 37 | } 38 | catch(e) { 39 | console.error("Logging error",e,type,...val) 40 | } 41 | 42 | } 43 | 44 | module.exports = {log,warn,info,error} -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "dotenv": "^10.0.0", 4 | "md5": "^2.3.0", 5 | "mysql2": "^3.9.7", 6 | "qs": "^6.12.0", 7 | "redis": "^3.1.2", 8 | "xmlbuilder": "^15.1.1" 9 | }, 10 | "version": "1.0.3", 11 | "name": "dvbi-csr-api", 12 | "description": "DVB-I CSR query API" 13 | } 14 | -------------------------------------------------------------------------------- /api/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | aws-ssl-profiles@^1.1.1: 6 | version "1.1.2" 7 | resolved "https://registry.yarnpkg.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz#157dd77e9f19b1d123678e93f120e6f193022641" 8 | integrity sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g== 9 | 10 | call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: 11 | version "1.0.2" 12 | resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" 13 | integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== 14 | dependencies: 15 | es-errors "^1.3.0" 16 | function-bind "^1.1.2" 17 | 18 | call-bound@^1.0.2: 19 | version "1.0.4" 20 | resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" 21 | integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== 22 | dependencies: 23 | call-bind-apply-helpers "^1.0.2" 24 | get-intrinsic "^1.3.0" 25 | 26 | charenc@0.0.2: 27 | version "0.0.2" 28 | resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" 29 | integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== 30 | 31 | crypt@0.0.2: 32 | version "0.0.2" 33 | resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" 34 | integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== 35 | 36 | denque@^1.5.0: 37 | version "1.5.1" 38 | resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" 39 | integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== 40 | 41 | denque@^2.1.0: 42 | version "2.1.0" 43 | resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" 44 | integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== 45 | 46 | dotenv@^10.0.0: 47 | version "10.0.0" 48 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" 49 | integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== 50 | 51 | dunder-proto@^1.0.1: 52 | version "1.0.1" 53 | resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" 54 | integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== 55 | dependencies: 56 | call-bind-apply-helpers "^1.0.1" 57 | es-errors "^1.3.0" 58 | gopd "^1.2.0" 59 | 60 | es-define-property@^1.0.1: 61 | version "1.0.1" 62 | resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" 63 | integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== 64 | 65 | es-errors@^1.3.0: 66 | version "1.3.0" 67 | resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" 68 | integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== 69 | 70 | es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: 71 | version "1.1.1" 72 | resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" 73 | integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== 74 | dependencies: 75 | es-errors "^1.3.0" 76 | 77 | function-bind@^1.1.2: 78 | version "1.1.2" 79 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" 80 | integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== 81 | 82 | generate-function@^2.3.1: 83 | version "2.3.1" 84 | resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" 85 | integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== 86 | dependencies: 87 | is-property "^1.0.2" 88 | 89 | get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: 90 | version "1.3.0" 91 | resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" 92 | integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== 93 | dependencies: 94 | call-bind-apply-helpers "^1.0.2" 95 | es-define-property "^1.0.1" 96 | es-errors "^1.3.0" 97 | es-object-atoms "^1.1.1" 98 | function-bind "^1.1.2" 99 | get-proto "^1.0.1" 100 | gopd "^1.2.0" 101 | has-symbols "^1.1.0" 102 | hasown "^2.0.2" 103 | math-intrinsics "^1.1.0" 104 | 105 | get-proto@^1.0.1: 106 | version "1.0.1" 107 | resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" 108 | integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== 109 | dependencies: 110 | dunder-proto "^1.0.1" 111 | es-object-atoms "^1.0.0" 112 | 113 | gopd@^1.2.0: 114 | version "1.2.0" 115 | resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" 116 | integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== 117 | 118 | has-symbols@^1.1.0: 119 | version "1.1.0" 120 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" 121 | integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== 122 | 123 | hasown@^2.0.2: 124 | version "2.0.2" 125 | resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" 126 | integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== 127 | dependencies: 128 | function-bind "^1.1.2" 129 | 130 | iconv-lite@^0.6.3: 131 | version "0.6.3" 132 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" 133 | integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== 134 | dependencies: 135 | safer-buffer ">= 2.1.2 < 3.0.0" 136 | 137 | is-buffer@~1.1.6: 138 | version "1.1.6" 139 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" 140 | integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== 141 | 142 | is-property@^1.0.2: 143 | version "1.0.2" 144 | resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" 145 | integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== 146 | 147 | long@^5.2.1: 148 | version "5.3.2" 149 | resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" 150 | integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== 151 | 152 | lru-cache@^7.14.1: 153 | version "7.18.3" 154 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" 155 | integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== 156 | 157 | lru.min@^1.0.0: 158 | version "1.1.2" 159 | resolved "https://registry.yarnpkg.com/lru.min/-/lru.min-1.1.2.tgz#01ce1d72cc50c7faf8bd1f809ebf05d4331021eb" 160 | integrity sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg== 161 | 162 | math-intrinsics@^1.1.0: 163 | version "1.1.0" 164 | resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" 165 | integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== 166 | 167 | md5@^2.3.0: 168 | version "2.3.0" 169 | resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" 170 | integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== 171 | dependencies: 172 | charenc "0.0.2" 173 | crypt "0.0.2" 174 | is-buffer "~1.1.6" 175 | 176 | mysql2@^3.9.7: 177 | version "3.14.1" 178 | resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.14.1.tgz#7786160abf086fd279e0253e16e34c05b4ab3b3e" 179 | integrity sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w== 180 | dependencies: 181 | aws-ssl-profiles "^1.1.1" 182 | denque "^2.1.0" 183 | generate-function "^2.3.1" 184 | iconv-lite "^0.6.3" 185 | long "^5.2.1" 186 | lru.min "^1.0.0" 187 | named-placeholders "^1.1.3" 188 | seq-queue "^0.0.5" 189 | sqlstring "^2.3.2" 190 | 191 | named-placeholders@^1.1.3: 192 | version "1.1.3" 193 | resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351" 194 | integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w== 195 | dependencies: 196 | lru-cache "^7.14.1" 197 | 198 | object-inspect@^1.13.3: 199 | version "1.13.4" 200 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" 201 | integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== 202 | 203 | qs@^6.12.0: 204 | version "6.14.0" 205 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" 206 | integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== 207 | dependencies: 208 | side-channel "^1.1.0" 209 | 210 | redis-commands@^1.7.0: 211 | version "1.7.0" 212 | resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" 213 | integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ== 214 | 215 | redis-errors@^1.0.0, redis-errors@^1.2.0: 216 | version "1.2.0" 217 | resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" 218 | integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== 219 | 220 | redis-parser@^3.0.0: 221 | version "3.0.0" 222 | resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" 223 | integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== 224 | dependencies: 225 | redis-errors "^1.0.0" 226 | 227 | redis@^3.1.2: 228 | version "3.1.2" 229 | resolved "https://registry.yarnpkg.com/redis/-/redis-3.1.2.tgz#766851117e80653d23e0ed536254677ab647638c" 230 | integrity sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw== 231 | dependencies: 232 | denque "^1.5.0" 233 | redis-commands "^1.7.0" 234 | redis-errors "^1.2.0" 235 | redis-parser "^3.0.0" 236 | 237 | "safer-buffer@>= 2.1.2 < 3.0.0": 238 | version "2.1.2" 239 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 240 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 241 | 242 | seq-queue@^0.0.5: 243 | version "0.0.5" 244 | resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" 245 | integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q== 246 | 247 | side-channel-list@^1.0.0: 248 | version "1.0.0" 249 | resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" 250 | integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== 251 | dependencies: 252 | es-errors "^1.3.0" 253 | object-inspect "^1.13.3" 254 | 255 | side-channel-map@^1.0.1: 256 | version "1.0.1" 257 | resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" 258 | integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== 259 | dependencies: 260 | call-bound "^1.0.2" 261 | es-errors "^1.3.0" 262 | get-intrinsic "^1.2.5" 263 | object-inspect "^1.13.3" 264 | 265 | side-channel-weakmap@^1.0.2: 266 | version "1.0.2" 267 | resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" 268 | integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== 269 | dependencies: 270 | call-bound "^1.0.2" 271 | es-errors "^1.3.0" 272 | get-intrinsic "^1.2.5" 273 | object-inspect "^1.13.3" 274 | side-channel-map "^1.0.1" 275 | 276 | side-channel@^1.1.0: 277 | version "1.1.0" 278 | resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" 279 | integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== 280 | dependencies: 281 | es-errors "^1.3.0" 282 | object-inspect "^1.13.3" 283 | side-channel-list "^1.0.0" 284 | side-channel-map "^1.0.1" 285 | side-channel-weakmap "^1.0.2" 286 | 287 | sqlstring@^2.3.2: 288 | version "2.3.3" 289 | resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.3.tgz#2ddc21f03bce2c387ed60680e739922c65751d0c" 290 | integrity sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg== 291 | 292 | xmlbuilder@^15.1.1: 293 | version "15.1.1" 294 | resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" 295 | integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== 296 | -------------------------------------------------------------------------------- /backend/app/controllers/eventhistory.controller.js: -------------------------------------------------------------------------------- 1 | const { log } = require("../../logging.js"); 2 | const EventHistory = require("../models/eventhistory.model.js"); 3 | 4 | // create, called only by other internal routes 5 | exports.create = (data, result) => { 6 | // Validate request 7 | if (!data) { 8 | result("err: data is null", null); 9 | return; 10 | } 11 | const event = new EventHistory({ 12 | User: data.user.Id, 13 | UserName: data.user.Name, 14 | Event: data.eventType || "Create", 15 | Time: Date.now(), 16 | ServiceList: data.id || 0, 17 | Name: data.Name || "", 18 | ContentJson: data.ContentJson || "", 19 | }); 20 | 21 | EventHistory.create(event, (err, data) => { 22 | if (err) { 23 | log(err); 24 | result(err, null); 25 | return; 26 | } 27 | 28 | if (!data) { 29 | result("EventHistory. create err: null data", null); 30 | return; 31 | } 32 | 33 | result(null, data); 34 | }); 35 | }; 36 | 37 | // Find event of a single List with a listId 38 | exports.findOne = (req, res) => { 39 | EventHistory.findById(req.params.listId, (err, data) => { 40 | if (err) { 41 | if (err.Name === "not_found") { 42 | res.send([]); 43 | } else { 44 | res.status(500).send({ 45 | message: "Error retrieving List with id " + req.params.listId, 46 | }); 47 | } 48 | } else res.send(data); 49 | }); 50 | }; 51 | -------------------------------------------------------------------------------- /backend/app/controllers/listprovider.controller.js: -------------------------------------------------------------------------------- 1 | const { log } = require("../../logging.js"); 2 | const Listprovider = require("../models/listprovider.model.js"); 3 | 4 | // Retrieve all Providers from the database. 5 | exports.getProvider = (req, res) => { 6 | Listprovider.getProvider((err, data) => { 7 | if (err) { 8 | log(err); 9 | res.status(500).send({ 10 | message: err.message || "Some error occurred while getting listprovider.", 11 | }); 12 | } else res.send(data); 13 | }); 14 | }; 15 | 16 | // Update a Customer identified by the customerId in the request 17 | exports.update = (req, res) => { 18 | // Validate Request 19 | if (!req.body) { 20 | res.status(400).send({ 21 | message: "Content can not be empty!", 22 | }); 23 | } 24 | 25 | Listprovider.update(new Listprovider(req.body), (err, data) => { 26 | if (err) { 27 | if (err.kind === "not_found") { 28 | res.status(404).send({ 29 | message: `List provider not found`, 30 | }); 31 | } else { 32 | log(err); 33 | if (err.message) { 34 | res.status(500).send({ 35 | message: "Error: " + err.message, 36 | }); 37 | } else { 38 | res.status(500).send({ 39 | message: "Error updating listprovider", 40 | }); 41 | } 42 | } 43 | } else res.send(data); 44 | }); 45 | }; 46 | -------------------------------------------------------------------------------- /backend/app/controllers/provider.controller.js: -------------------------------------------------------------------------------- 1 | const Provider = require("../models/provider.model.js"); 2 | const ServiceList = require("./servicelist.controller"); 3 | const User = require("../models/user.model.js"); 4 | const { log } = require("../../logging.js"); 5 | 6 | // Create and Save a new Customer 7 | exports.create = (req, res) => { 8 | // Validate request 9 | if (!req.body) { 10 | res.status(400).send({ 11 | message: "Content can not be empty!", 12 | }); 13 | } 14 | 15 | // Create a Customer 16 | const provider = new Provider({ 17 | Kind: req.body.Kind, 18 | ContactName: req.body.ContactName, 19 | Jurisdiction: req.body.Jurisdiction, 20 | Address: req.body.Address, 21 | ElectronicAddress: req.body.ElectronicAddress, 22 | Regulator: req.body.Regulator, 23 | Icons: req.body.Icons, 24 | }); 25 | 26 | const Names = req.body.Names; 27 | 28 | // Save Provider in the database 29 | Provider.create(provider, Names, (err, data) => { 30 | if (err) 31 | res.status(500).send({ 32 | message: err.message || "Some error occurred while creating the Provider.", 33 | }); 34 | else { 35 | if (req.user.Role != "admin") { 36 | // add the new provider to users' providers 37 | let updateData = req.user; 38 | try { 39 | updateData.Providers.push(data.id); 40 | } catch(err) { 41 | log(err); 42 | } 43 | User.updateById(req.user.Id, false, new User(updateData), (err, data) => { 44 | if (err) { 45 | log(err); 46 | } 47 | }); 48 | } 49 | 50 | res.send(data); 51 | } 52 | }); 53 | }; 54 | 55 | // Retrieve all Providers from the database. 56 | exports.findAll = async (req, res) => { 57 | if (req.user && req.user.Role === "admin") { 58 | Provider.getAll((err, data) => { 59 | if (err) 60 | res.status(500).send({ 61 | message: err.message || "Some error occurred while retrieving providers.", 62 | }); 63 | else res.send(data); 64 | }); 65 | } else { 66 | // fetch all providers for this user 67 | let provs = req.user.Providers ?? []; 68 | 69 | let result_data = []; 70 | let promises = []; 71 | for (const p in provs) { 72 | promises.push( 73 | new Promise((resolve, reject) => { 74 | Provider.findById(provs[p], (err, data) => { 75 | if (err) { 76 | reject(err); 77 | log(err); 78 | } else { 79 | result_data.push(data); 80 | resolve(); 81 | } 82 | }); 83 | }).catch((err) => { 84 | log(err); 85 | }) 86 | ); 87 | } 88 | 89 | await Promise.all(promises).catch((err) => log(err)); 90 | res.send(result_data); 91 | 92 | // TODO: should we remove this id from users proders-set? 93 | } 94 | }; 95 | 96 | // Find a single Provider with a id 97 | exports.findOne = (req, res) => { 98 | let validrequest = true; 99 | 100 | // providerId is a valid number 101 | const providerId = req.params.customerId; 102 | if (isNaN(providerId)) { 103 | validrequest = false; 104 | } 105 | 106 | // check user has rigths to this prov 107 | if (req.user.Role !== "admin") { 108 | let providers = req.user.Providers ?? []; 109 | if (!providers.includes(+providerId)) { 110 | validrequest = false; 111 | } 112 | } 113 | 114 | if (!validrequest) { 115 | res.status(400).send({ 116 | message: "Invalid request!", 117 | }); 118 | return; 119 | } 120 | 121 | Provider.findById(req.params.customerId, (err, data) => { 122 | if (err) { 123 | if (err.kind === "not_found") { 124 | res.status(404).send({ 125 | message: `Not found Customer with id ${req.params.customerId}.`, 126 | }); 127 | } else { 128 | res.status(500).send({ 129 | message: "Error retrieving Customer with id " + req.params.customerId, 130 | }); 131 | } 132 | } else res.send(data); 133 | }); 134 | }; 135 | 136 | // Update a Customer identified by the customerId in the request 137 | exports.update = (req, res) => { 138 | let validrequest = true; 139 | 140 | // providerId is a valid number 141 | const providerId = req.params.customerId; 142 | if (isNaN(providerId)) { 143 | validrequest = false; 144 | } 145 | 146 | // check user has rigths to this prov 147 | if (req.user.Role !== "admin") { 148 | let providers = req.user.Providers ?? []; 149 | 150 | if (!providers.includes(+providerId)) { 151 | validrequest = false; 152 | } 153 | } 154 | 155 | if (!validrequest) { 156 | res.status(400).send({ 157 | message: "Invalid request!", 158 | }); 159 | return; 160 | } 161 | 162 | Provider.updateById(req.params.customerId, new Provider(req.body), (err, data) => { 163 | if (err) { 164 | if (err.kind === "not_found") { 165 | res.status(404).send({ 166 | message: `Not found Provider with id ${req.params.customerId}.`, 167 | }); 168 | } else { 169 | res.status(500).send({ 170 | message: err.message || "Some error occurred while updating the Provider.", 171 | }); 172 | } 173 | } else res.send(data); 174 | }); 175 | }; 176 | 177 | // Delete a Provider with the specified customerId in the request 178 | exports.delete = async (req, res) => { 179 | // check user has rigths to this prov 180 | if (req.user.Role != "admin") { 181 | let providers = req.user.Providers ??[]; 182 | 183 | if (!providers.includes(+req.params.customerId)) { 184 | res.status(400).send({ 185 | message: "Invalid request!", 186 | }); 187 | return; 188 | } 189 | } 190 | 191 | await ServiceList.deleteProviderLists(req, req.params.customerId); 192 | 193 | Provider.remove(req.params.customerId, (err, data) => { 194 | if (err) { 195 | if (err.kind === "not_found") { 196 | res.status(404).send({ 197 | message: `Not found Provider with id ${req.params.customerId}.`, 198 | }); 199 | } else { 200 | res.status(500).send({ 201 | message: "Could not delete Provider with id " + req.params.customerId, 202 | }); 203 | } 204 | } else res.send({ message: `Provider was deleted successfully!` }); 205 | }); 206 | }; 207 | -------------------------------------------------------------------------------- /backend/app/controllers/servicelist.controller.js: -------------------------------------------------------------------------------- 1 | const ServiceList = require("../models/servicelist.model.js"); 2 | const EventHistory = require("../controllers/eventhistory.controller"); 3 | const Providers = require("../controllers/provider.controller"); 4 | const { log } = require("../../logging.js"); 5 | 6 | // Create and Save a new List 7 | exports.create = (req, res) => { 8 | // better validation, TODO 9 | 10 | if (!req.body) { 11 | res.status(400).send({ 12 | message: "Content can not be empty!", 13 | }); 14 | return; 15 | } 16 | 17 | // provider check, user must "own" the provider to add a new list 18 | let provs = req.user.Providers ?? []; 19 | if (!provs.includes(req.body.ProviderId) && req.user.Role !== "admin") { 20 | res.status(400).send({ 21 | message: "Invalid request", 22 | }); 23 | return; 24 | } 25 | 26 | // Create a List 27 | const serviceList = new ServiceList({ 28 | Names: req.body.Names, 29 | URI: req.body.URI, 30 | languages: req.body.languages, 31 | Provider: req.body.Provider, 32 | ProviderId: req.body.ProviderId, 33 | regulatorList: req.body.regulatorList, 34 | Delivery: req.body.Delivery, 35 | targetCountries: req.body.targetCountries, 36 | Genres: req.body.Genres, 37 | Status: "active", 38 | ServiceListId: req.body.ServiceListId, 39 | Icons: req.body.Icons 40 | }); 41 | 42 | // Save List in the database 43 | ServiceList.create(serviceList, (err, data) => { 44 | if (err) 45 | res.status(500).send({ 46 | message: "Error creating List" + (err.msg ? ": " + err.msg : ""), 47 | }); 48 | else { 49 | res.send(data); 50 | 51 | // Log an event for this serviceList's event history 52 | const event = { 53 | ...data, 54 | Name: req.body.Names[0].name, 55 | user: req.user, 56 | eventType: "Create", 57 | //ContentJson: "" // this field for actual update contents, possible undo feature 58 | }; 59 | 60 | EventHistory.create(event, (err, res) => { 61 | if (err) { 62 | log(err); 63 | } 64 | }); 65 | } 66 | }); 67 | }; 68 | 69 | // Retrieve all Lists from the database. 70 | exports.findAll = async (req, res) => { 71 | // check that user is admin 72 | if (!req.user) { 73 | res.status(500).send({ 74 | message: "Not authorized.", 75 | }); 76 | return; 77 | } 78 | 79 | if (req.user.Role == "admin") { 80 | ServiceList.getAll((err, data) => { 81 | if (err) 82 | res.status(500).send({ 83 | message: err.message || "Some error occurred while retrieving lists.", 84 | }); 85 | else res.send(data); 86 | }); 87 | } else { 88 | // get users providers and their lists 89 | let provs = req.user.Providers ?? []; 90 | let lists = []; 91 | let promises = []; 92 | for (const p in provs) { 93 | promises.push( 94 | new Promise((resolve, reject) => { 95 | ServiceList.getAllByProvider(provs[p], (err, data) => { 96 | if (err) { 97 | reject(err); 98 | log(err); 99 | } else { 100 | data.forEach((dd) => lists.push(dd)); 101 | resolve(); 102 | } 103 | }); 104 | }).catch((err) => { 105 | log(err); 106 | }) 107 | ); 108 | } 109 | await Promise.all(promises).catch((err) => log(err)); 110 | res.send(lists); 111 | } 112 | }; 113 | 114 | // Retrieve all Lists from the database. 115 | exports.findAllByProvider = (req, res) => { 116 | 117 | const providerId = +req.params.providerId; // sanitize... 118 | const user = req.user; 119 | let provs = req.user.Providers ?? []; 120 | if (provs.includes(providerId) || user.Role == "admin") { 121 | let provider = providerId; 122 | 123 | ServiceList.getAllByProvider(provider, (err, data) => { 124 | if (err) 125 | res.status(500).send({ 126 | message: err.message || "Some error occurred while retrieving lists.", 127 | }); 128 | else res.send(data); 129 | }); 130 | } else { 131 | res.status(401).send({ 132 | message: "Unauthorised", 133 | }); 134 | } 135 | }; 136 | 137 | // find with status and by provider (optonal) 138 | // 139 | exports.findWithStatus = (req, result, status = "active", provider = null) => { 140 | ServiceList.getAllByStatus( 141 | (err, data) => { 142 | if (err) result(err, null); 143 | else result(null, data); 144 | }, 145 | status, 146 | provider 147 | ); // is status is null defaults to "active" 148 | }; 149 | 150 | // Find a single List with a listId 151 | exports.findOne = (req, res) => { 152 | let validrequest = true; 153 | 154 | // Id is a valid number 155 | const listId = req.params.listId; 156 | if (isNaN(listId)) { 157 | validrequest = false; 158 | } 159 | 160 | if (!validrequest) { 161 | res.status(400).send({ 162 | message: "Invalid request!", 163 | }); 164 | return; 165 | } 166 | 167 | ServiceList.findById(req.params.listId, (err, data) => { 168 | if (err) { 169 | if (err.Name === "not_found") { 170 | res.status(404).send({ 171 | message: `Not found List with id ${req.params.listId}.`, 172 | }); 173 | } else { 174 | res.status(500).send({ 175 | message: "Error retrieving List with id " + req.params.listId, 176 | }); 177 | } 178 | } else { 179 | // check if the found list is by one of the user's providers (if not admin) 180 | if (req.user.Role !== "admin") { 181 | let providers = req.user.Providers ?? []; 182 | if (!providers.includes(data.ProviderId)) { 183 | res.status(400).send({ 184 | message: "Invalid request!", 185 | }); 186 | return; 187 | } 188 | } 189 | 190 | res.send(data); 191 | } 192 | }); 193 | }; 194 | 195 | exports.findById = (req, res) => { 196 | let validrequest = true; 197 | 198 | // Id is a valid number 199 | const listId = req.params.listId; 200 | if (!listId) { 201 | validrequest = false; 202 | } 203 | 204 | if (!validrequest) { 205 | res.status(400).send({ 206 | message: "Invalid request!", 207 | }); 208 | return; 209 | } 210 | 211 | ServiceList.findByUniqueId(req.params.listId, (err, data) => { 212 | if (err) { 213 | if (err.Name === "not_found") { 214 | res.status(404).send({ 215 | message: `Not found List with id ${req.params.listId}.`, 216 | }); 217 | } else { 218 | res.status(500).send({ 219 | message: "Error retrieving List with id " + req.params.listId, 220 | }); 221 | } 222 | } else { 223 | // check if the found list is by one of the user's providers (if not admin) 224 | if (req.user.Role !== "admin") { 225 | let providers = req.user.Providers ?? []; 226 | if (!providers.includes(data.ProviderId)) { 227 | res.status(400).send({ 228 | message: "Invalid request!", 229 | }); 230 | return; 231 | } 232 | } 233 | 234 | res.send(data); 235 | } 236 | }); 237 | }; 238 | // update 239 | exports.update = async (req, res) => { 240 | const listId = req.params.listId; 241 | 242 | if (!req.body) { 243 | res.status(400).send({ 244 | message: "Content can not be empty!", 245 | }); 246 | return; 247 | } 248 | 249 | if (isNaN(listId)) { 250 | res.status(400).send({ 251 | message: "Invalid request!", 252 | }); 253 | return; 254 | } 255 | 256 | // check users ownership of list's provider 257 | const listCheck = await ServiceList.listHeaderById(listId); 258 | let provs = req.user.Providers ?? []; 259 | let valid = true; 260 | 261 | if (listCheck) { 262 | if (!provs.includes(+listCheck.Provider) && req.user.Role != "admin") { 263 | valid = false; 264 | } 265 | } else valid = false; 266 | 267 | if (!valid) { 268 | res.status(400).send({ 269 | message: "Invalid request!", 270 | }); 271 | return; 272 | } 273 | 274 | ServiceList.updateById(listId, new ServiceList(req.body), (err, data) => { 275 | if (err) { 276 | res.status(500).send({ 277 | message: "Error updating List" + (err.msg ? ": " + err.msg : ""), 278 | }); 279 | } else { 280 | res.send(data); 281 | 282 | const event = { 283 | id: req.params.listId, 284 | user: req.user, 285 | eventType: "Update", 286 | Name: req.body.Names[0].name, 287 | //ContentJson: "" 288 | }; 289 | 290 | EventHistory.create(event, (err, res) => { 291 | if (err) { 292 | log(err); 293 | } 294 | }); 295 | } 296 | }); 297 | }; 298 | 299 | // delete 300 | exports.delete = async (req, res) => { 301 | const listId = req.params.listId; 302 | 303 | // check users ownership of list's provider 304 | const listCheck = await ServiceList.listHeaderById(listId); 305 | let provs = req.user.Providers ?? []; 306 | let valid = true; 307 | 308 | if (listCheck) { 309 | if (!provs.includes(+listCheck.Provider) && req.user.Role != "admin") { 310 | valid = false; 311 | } 312 | } else valid = false; 313 | 314 | if (!valid) { 315 | res.status(400).send({ 316 | message: "Invalid request!", 317 | }); 318 | return; 319 | } 320 | 321 | ServiceList.remove(req.params.listId, (err, data) => { 322 | if (err) { 323 | if (err.list === "not_found") { 324 | res.status(404).send({ 325 | message: `Not found List with id ${req.params.listId}.`, 326 | }); 327 | } else { 328 | res.status(500).send({ 329 | message: "Could not delete List with id " + req.params.listId, 330 | }); 331 | } 332 | } else { 333 | res.send({ message: `List was deleted successfully!` }); 334 | 335 | const event = { 336 | id: req.params.listId, 337 | user: req.user, 338 | eventType: "Delete", 339 | }; 340 | 341 | EventHistory.create(event, (err, res) => { 342 | if (err) { 343 | log(err); 344 | } 345 | }); 346 | } 347 | }); 348 | }; 349 | 350 | // delete all per provider 351 | exports.deleteProviderLists = async (req, providerId) => { 352 | // get provider lists 353 | const data = await ServiceList.getAllProviderServiceListOfferings(providerId).catch((err) => { 354 | log(err); 355 | }); 356 | 357 | if (data) { 358 | let promises = []; 359 | 360 | for (let i = 0; i < data.length; i++) { 361 | promises.push( 362 | new Promise((resolve, reject) => { 363 | let listId = data[i].Id; 364 | ServiceList.remove(listId, (err, result) => { 365 | if (err) { 366 | reject(err); 367 | } else { 368 | resolve(); 369 | 370 | const event = { 371 | id: listId, 372 | user: req.user, 373 | eventType: "Delete", 374 | }; 375 | 376 | EventHistory.create(event, (err, res) => { 377 | if (err) { 378 | log(err); 379 | } 380 | }); 381 | } 382 | }); 383 | }).catch((err) => { 384 | log(err); 385 | }) 386 | ); 387 | } 388 | 389 | await Promise.all(promises).catch((err) => log(err)); 390 | } 391 | }; 392 | -------------------------------------------------------------------------------- /backend/app/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/user.model.js"); 2 | const bcrypt = require("bcryptjs"); 3 | const jwt = require("jsonwebtoken"); 4 | const { log } = require("../../logging.js"); 5 | 6 | // create 7 | exports.create = async (req, res) => { 8 | if (req.user.Role !== "admin") { 9 | return res.status(401).json({ success: false }); 10 | } 11 | 12 | const { Name, Role, Password, Email, Providers } = req.body; 13 | let valid = true; 14 | 15 | if (!Name || !Name.length) { 16 | valid = false; 17 | } 18 | 19 | if (!valid) { 20 | return res.status(500).json({ success: false }); 21 | } 22 | //Empty password hash for publisher role. no password login, only access token 23 | const passwordHash = Role != "publisher" ? await bcrypt.hash(Password, 8) : ''; 24 | 25 | const user = new User({ 26 | Name: Name, 27 | Email: Email, 28 | passwordhash: passwordHash, 29 | Role: Role, 30 | Organization: 0, 31 | Providers: JSON.stringify(Providers) || '[]', 32 | Session: 1, 33 | }); 34 | 35 | const newUser = await User.create(user); 36 | if (newUser) { 37 | return res.status(200).json({ success: true, Id: newUser.insertId }); 38 | } else { 39 | return res.status(500).json({ success: false }); 40 | } 41 | }; 42 | 43 | exports.findOne = (req, res) => { 44 | // privileges: user should be able to find their own data 45 | 46 | User.findById(req.params.userId) 47 | .then((data) => { 48 | res.send(data); 49 | }) 50 | .catch((err) => { 51 | if (err.Name === "not_found") { 52 | res.status(404).send({ 53 | message: `Not found user with id ${req.params.userId}.`, 54 | }); 55 | } else { 56 | res.status(500).send({ 57 | message: "Error retrieving user with id " + req.params.userId, 58 | }); 59 | } 60 | }); 61 | }; 62 | 63 | exports.getCurrent = (req,res) => { 64 | const id = req.user.Id; 65 | User.findById(id) 66 | .then((data) => { 67 | delete data[0]["Session"]; 68 | res.send(data); 69 | }) 70 | .catch((err) => { 71 | if (err.Name === "not_found") { 72 | res.status(404).send({ 73 | message: `Not found user with id ${req.params.userId}.`, 74 | }); 75 | } else { 76 | res.status(500).send({ 77 | message: "Error retrieving user with id " + req.params.userId, 78 | }); 79 | } 80 | }); 81 | 82 | } 83 | 84 | // find all users 85 | exports.findAll = (req, res) => { 86 | if (!req.user) { 87 | res.status(500).send({ 88 | message: err.message || "Not authorized.", 89 | }); 90 | return; 91 | } 92 | 93 | // check that user is admin 94 | if (req.user.Role == "admin") { 95 | User.getAll() 96 | .then((data) => { 97 | const from = data.findIndex((user) => user.Id === req.user.Id) 98 | data.splice(0, 0, data.splice(from, 1)[0]); 99 | res.send(data); 100 | }) 101 | .catch((err) => { 102 | res.status(500).send({ 103 | message: err.message || "Some error occurred while retrieving users.", 104 | }); 105 | }); 106 | } else { 107 | // return just users own data 108 | const id = req.user.Id; 109 | User.findById(id) 110 | .then((data) => { 111 | delete data[0]["Session"]; 112 | delete data[0]["Role"]; 113 | res.send(data); 114 | }) 115 | .catch((err) => { 116 | if (err.Name === "not_found") { 117 | res.status(404).send({ 118 | message: `Not found user with id ${req.params.userId}.`, 119 | }); 120 | } else { 121 | res.status(500).send({ 122 | message: "Error retrieving user with id " + req.params.userId, 123 | }); 124 | } 125 | }); 126 | } 127 | }; 128 | 129 | // Retrieve all for a provider 130 | exports.findAllByProvider = (req, res) => { 131 | const providerId = +req.params.providerId; 132 | const user = req.user; 133 | 134 | let provs = req.user.Providers ?? []; 135 | 136 | if (provs.includes(providerId) || user.Role == "admin") { 137 | let provider = providerId; 138 | 139 | User.getAllByProvider(provider, (err, data) => { 140 | if (err) 141 | res.status(500).send({ 142 | message: err.message || "Some error occurred while retrieving lists.", 143 | }); 144 | else res.send(data); 145 | }); 146 | } else { 147 | res.status(401).send({ 148 | message: "Unauthorised", 149 | }); 150 | } 151 | }; 152 | 153 | exports.changePassword = async (req, res) => { 154 | let validrequest = true; 155 | 156 | if (!req.body) { 157 | validrequest = false; 158 | } 159 | 160 | if (!validrequest) { 161 | res.status(400).send({ 162 | message: "Invalid request!", 163 | }); 164 | return; 165 | } 166 | 167 | let updateData = req.body; 168 | 169 | const user = await User.findByName(req.user.Name); 170 | 171 | if (!user || user.length < 1) { 172 | return res.status(401).json({ success: false, error: "general error" }); 173 | } 174 | //Admin password reset 175 | if (req.user.Role === "admin" && updateData.CurrentUser) { 176 | const newHash = await bcrypt.hash(updateData.NewPassword, 8); 177 | User.updatePwd(updateData.CurrentUser, newHash, (err, data) => { 178 | if (err) { 179 | res.status(500).send({ 180 | message: "Error updating User with id " + req.user.Id, 181 | }); 182 | } else { 183 | res.send(data); 184 | } 185 | }); 186 | } else { 187 | const { Id, Hash } = user[0]; 188 | 189 | const match = await bcrypt.compare(updateData.Password, Hash); 190 | if (!match) { 191 | // old pwd is incorrect 192 | return res.status(401).json({ success: false, error: "general error" }); 193 | } 194 | 195 | // get new hash 196 | const newHash = await bcrypt.hash(updateData.NewPassword, 8); 197 | 198 | // update new password hash 199 | User.updatePwd(Id, newHash, (err, data) => { 200 | if (err) { 201 | res.status(500).send({ 202 | message: "Error updating User with id " + req.user.Id, 203 | }); 204 | } else { 205 | res.send(data); 206 | } 207 | }); 208 | } 209 | }; 210 | 211 | // update 212 | exports.update = (req, res) => { 213 | let validrequest = true; 214 | let updateData = req.body; 215 | 216 | const userId = req.params.userId; 217 | 218 | if (!req.body) { 219 | validrequest = false; 220 | } 221 | 222 | if (isNaN(userId)) { 223 | validrequest = false; 224 | } 225 | 226 | // a user can only update their own email & remove providers (add providers is elsewhere) 227 | // (change passwd is done elsewhere) 228 | if (req.user.Role === "admin" || req.user.Id === updateData.Id) { 229 | if (req.user.Role !== "admin") { 230 | // check providers, can only remove providers from existing 231 | try { 232 | const oldlist = req.user.Providers ?? []; 233 | const newlist = updateData.Providers ?? []; 234 | 235 | newlist.forEach((elem) => { 236 | if (!oldlist.includes(elem)) { 237 | validrequest = false; 238 | } 239 | }); 240 | } catch (err) { 241 | log(err); 242 | validrequest = false; 243 | } 244 | } 245 | } else { 246 | validrequest = false; 247 | } 248 | 249 | if (!validrequest) { 250 | res.status(400).send({ 251 | message: "Invalid request!", 252 | }); 253 | return; 254 | } 255 | 256 | User.updateById(userId, req.user.Role === "admin", new User(updateData), (err, data) => { 257 | if (err) { 258 | res.status(500).send({ 259 | message: "Error updating User with id " + req.params.userId, 260 | }); 261 | } else { 262 | res.send(data); 263 | } 264 | }); 265 | }; 266 | 267 | // remove 268 | exports.delete = (req, res) => { 269 | let validrequest = true; 270 | const userId = req.params.userId; 271 | 272 | if (isNaN(userId)) { 273 | validrequest = false; 274 | } 275 | 276 | if (req.user.Role !== "admin") { 277 | validrequest = false; 278 | } 279 | 280 | if (!validrequest) { 281 | res.status(400).send({ 282 | message: "Invalid request!", 283 | }); 284 | return; 285 | } 286 | 287 | User.remove(req.params.userId, (err, data) => { 288 | if (err) { 289 | if (err.user === "not_found") { 290 | res.status(404).send({ 291 | message: `Not found User with id ${req.params.userId}.`, 292 | }); 293 | } else { 294 | res.status(500).send({ 295 | message: "Could not delete User with id " + req.params.userId, 296 | }); 297 | } 298 | } else { 299 | res.send({ message: `User was deleted successfully!` }); 300 | } 301 | }); 302 | }; 303 | 304 | // log out 305 | exports.logout = (req, res) => { 306 | const { Id, Session } = req.user; 307 | 308 | let newSession = Session + 1; 309 | if (newSession > 1000) newSession = 1; 310 | 311 | User.updateSession(Id, newSession, (err, data) => { 312 | if (err) { 313 | log(err); 314 | res.status(400).json({ success: false }); 315 | } else { 316 | res.send({ success: true }); 317 | } 318 | }); 319 | }; 320 | 321 | exports.getPublishToken = async (req, res) => { 322 | if (req.user.Role !== "admin") { 323 | res.status(400).send({ 324 | message: "Invalid request!", 325 | }); 326 | return; 327 | } 328 | const userId = req.params.userId; 329 | const user = await User.findById(userId); 330 | 331 | if (!user || !user[0]) { 332 | res.status(400).send({ 333 | message: "Invalid request!", 334 | }); 335 | return; 336 | } 337 | 338 | const { Id, Session,Role } = user[0]; 339 | 340 | if (Role !== "publisher") { 341 | res.status(400).send({ 342 | message: "User is not a publisher!", 343 | }); 344 | return; 345 | } 346 | const date = new Date(2100,0, 1, 0, 0, 0, 0) 347 | const iat = new Date(2025,0, 1, 0, 0, 0, 0) 348 | 349 | const token = jwt.sign({ Id, Role, Session,iat:Math.floor(iat.getTime()/1000),exp:Math.floor(date.getTime()/1000) }, req.app.get("jwtstring")); 350 | res.send({ token: token }); 351 | }; 352 | 353 | exports.refreshPublishToken = async (req, res) => { 354 | const userId = req.params.userId; 355 | const user = await User.findById(userId); 356 | if (!user || !user[0]) { 357 | res.status(400).send({ 358 | message: "Invalid request!", 359 | }); 360 | return; 361 | } 362 | const { Id, Session,Role } = user[0]; 363 | if (Role !== "publisher") { 364 | res.status(400).send({ 365 | message: "User is not a publisher!", 366 | }); 367 | return; 368 | } 369 | 370 | let newSession = Session + 1; 371 | if (newSession > 1000) newSession = 1; 372 | 373 | User.updateSession(Id, newSession, (err, data) => { 374 | if (err) { 375 | res.status(400).json({ success: false }); 376 | } else { 377 | this.getPublishToken(req,res) 378 | } 379 | }); 380 | }; 381 | -------------------------------------------------------------------------------- /backend/app/models/db.js: -------------------------------------------------------------------------------- 1 | const mysql = require("mysql2"); 2 | const { info } = require("../../logging"); 3 | 4 | // Create a connection to the database 5 | const connection = mysql.createPool({ 6 | connectionLimit: process.env.DB_CONNECTIONS || 10, 7 | host: process.env.DB_HOST || "localhost", 8 | user: process.env.DB_USER || "user", 9 | port: process.env.DB_PORT || "", 10 | password: process.env.DB_PASSWORD || "password", 11 | database: process.env.DB_NAME || "dvb_i_csr", 12 | timezone: "Z", 13 | }); 14 | 15 | // open the MySQL connection 16 | connection.getConnection((error, connection) => { 17 | if (error) throw error; 18 | info("Successfully connected to the database."); 19 | connection.release(); 20 | }); 21 | 22 | module.exports = connection; 23 | -------------------------------------------------------------------------------- /backend/app/models/eventhistory.model.js: -------------------------------------------------------------------------------- 1 | const { log } = require("../../logging.js"); 2 | const sql = require("./db.js"); 3 | 4 | // constructor 5 | const EventHistory = function (eventHistory) { 6 | this.User = eventHistory.User; 7 | this.UserName = eventHistory.UserName; 8 | this.Event = eventHistory.Event; 9 | this.Time = eventHistory.Time; 10 | this.ServiceList = eventHistory.ServiceList; 11 | this.Name = eventHistory.Name; 12 | try { 13 | this.ContentJson = JSON.stringify(eventHistory.ContentJson); 14 | } catch { 15 | this.ContentJson = "[]"; 16 | } 17 | }; 18 | 19 | EventHistory.create = (newEvent, result) => { 20 | sql.query("INSERT INTO EventHistory SET ?", newEvent, (err, res) => { 21 | if (err) { 22 | log(err); 23 | result(err, null); 24 | return; 25 | } 26 | 27 | result(null, res); 28 | }); 29 | }; 30 | 31 | EventHistory.findById = (ListId, result) => { 32 | sql.query("SELECT * FROM EventHistory WHERE ServiceList = ?", ListId, async (err, res) => { 33 | if (err) { 34 | log(err); 35 | result(err, null); 36 | return; 37 | } 38 | 39 | if (res.length) { 40 | result(null, res); 41 | return; 42 | } 43 | 44 | result({ Name: "not_found" }, null); 45 | }); 46 | }; 47 | 48 | module.exports = EventHistory; 49 | -------------------------------------------------------------------------------- /backend/app/models/listprovider.model.js: -------------------------------------------------------------------------------- 1 | const { log } = require("../../logging.js"); 2 | const sql = require("./db.js"); 3 | 4 | // constructor 5 | const Listprovider = function (Provider) { 6 | this.Kind = Provider.Kind; 7 | this.ContactName = Provider.ContactName; 8 | this.Jurisdiction = Provider.Jurisdiction; 9 | this.Address = Provider.Address; 10 | this.ElectronicAddress = Provider.ElectronicAddress; 11 | this.Regulator = Provider.Regulator ? 1 : 0; 12 | this.Names = Provider.Names || []; 13 | this.Language = Provider.Language; 14 | this.Icons = Provider.Icons || []; 15 | }; 16 | 17 | Listprovider.getProvider = (result) => { 18 | sql.query( 19 | `SELECT Organization.Id,Organization.Kind,Organization.ContactName,Organization.Jurisdiction,Organization.Address,Organization.ElectronicAddress,Organization.Regulator,Organization.Icons,ServiceListEntryPoints.Language FROM ServiceListEntryPoints,Organization,EntityName WHERE ServiceListEntryPoints.Id = 1 AND ServiceListEntryPoints.ServiceListRegistryEntity = Organization.Id `, 20 | async (err, res) => { 21 | if (err) { 22 | log(err); 23 | result(err, null); 24 | return; 25 | } 26 | 27 | if (res.length) { 28 | res[0].Regulator = res[0].Regulator != 0; 29 | let provider = res[0]; 30 | const jsonfields = ["Address", "ContactName", "ElectronicAddress", "Jurisdiction", "Kind", "Icons"]; 31 | for (let field of jsonfields) { 32 | try { 33 | provider[field] = JSON.parse(provider[field]); 34 | } catch (e) { 35 | provider[field] = {} 36 | log(err); 37 | } 38 | } 39 | const names = await getNames(provider).catch((err) => { 40 | log(err); 41 | }); 42 | provider.Names = []; 43 | if (names) { 44 | names.forEach((n) => { 45 | provider.Names.push({ name: n.Name, type: n.Type }); 46 | }); 47 | } 48 | 49 | result(null, res[0]); 50 | return; 51 | } 52 | 53 | // not found Provider with the id 54 | result({ kind: "not_found" }, null); 55 | } 56 | ); 57 | }; 58 | 59 | Listprovider.update = (Provider, result) => { 60 | if (!Provider.Names || Provider.Names.length == 0 || Provider.Names[0].name == "") { 61 | result({ message: "Provider name required!" }, null); 62 | return; 63 | } 64 | sql.query("SELECT ServiceListRegistryEntity,Language FROM ServiceListEntryPoints WHERE Id = 1", (err, res) => { 65 | if (err) { 66 | log(err); 67 | result(err, null); 68 | return; 69 | } 70 | if (res[0].Language != Provider.Language) { 71 | sql.query("UPDATE ServiceListEntryPoints SET Language = ? WHERE Id = 1", Provider.Language); 72 | } 73 | const jsonfields = ["Address", "ContactName", "ElectronicAddress", "Jurisdiction", "Kind", "Icons"]; 74 | for (let field of jsonfields) { 75 | try { 76 | Provider[field] = JSON.stringify(Provider[field]); 77 | } catch (e) { 78 | log(e); 79 | result({message: "Invalid value in field:" +field}, null); 80 | return 81 | } 82 | } 83 | const orgId = res[0].ServiceListRegistryEntity; 84 | sql.query( 85 | "UPDATE Organization SET Kind = ?, ContactName = ?, Jurisdiction = ?,Address = ?,ElectronicAddress = ?,Regulator = ?,Icons = ? WHERE Id = ?", 86 | [ 87 | Provider.Kind, 88 | Provider.ContactName, 89 | Provider.Jurisdiction, 90 | Provider.Address, 91 | Provider.ElectronicAddress, 92 | Provider.Regulator ? 1 : 0, 93 | Provider.Icons, 94 | orgId, 95 | ], 96 | async (err, res) => { 97 | if (err) { 98 | log(err); 99 | result(err, null); 100 | return; 101 | } 102 | // update names 103 | await removeNames(orgId); 104 | await createNames(orgId, Provider); 105 | result(null, { ...Provider }); 106 | } 107 | ); 108 | }); 109 | }; 110 | 111 | function removeNames(id) { 112 | return new Promise((resolve, reject) => { 113 | sql.query(`DELETE FROM EntityName where EntityName.Organization = ${id}`, (err, res) => { 114 | if (err) { 115 | log(err); 116 | reject(err); 117 | } else { 118 | resolve(res); 119 | } 120 | }); 121 | }); 122 | } 123 | 124 | async function createNames(orgId, provider) { 125 | let promises = []; 126 | for (const index in provider.Names) { 127 | const data = { ...provider.Names[index], organization: orgId }; 128 | promises.push( 129 | new Promise((resolve, reject) => { 130 | sql.query("INSERT INTO EntityName SET ?", data, (err, res) => { 131 | if (err) { 132 | log(err); 133 | reject(); 134 | } 135 | resolve(); 136 | }); 137 | }).catch((err) => { 138 | return err; 139 | }) 140 | ); 141 | } 142 | await Promise.all(promises).catch((err) => log(err)); 143 | } 144 | 145 | function getNames(provider) { 146 | return new Promise((resolve, reject) => { 147 | sql.query(`SELECT * FROM EntityName where EntityName.Organization = ${provider.Id}`, (err, res) => { 148 | if (err) { 149 | log(err); 150 | reject(err); 151 | } else { 152 | resolve(res); 153 | } 154 | }); 155 | }); 156 | } 157 | 158 | module.exports = Listprovider; 159 | -------------------------------------------------------------------------------- /backend/app/models/provider.model.js: -------------------------------------------------------------------------------- 1 | const { log } = require("../../logging.js"); 2 | const sql = require("./db.js"); 3 | 4 | // constructor 5 | const Provider = function (Provider) { 6 | this.Kind = Provider.Kind; 7 | this.ContactName = Provider.ContactName; 8 | this.Jurisdiction = Provider.Jurisdiction; 9 | this.Address = Provider.Address; 10 | this.ElectronicAddress = Provider.ElectronicAddress; 11 | this.Regulator = Provider.Regulator ? 1 : 0; 12 | this.Names = Provider.Names || []; 13 | this.Icons = Provider.Icons || []; 14 | }; 15 | 16 | const jsonfields = [ 17 | "Address", 18 | "ContactName", 19 | "ElectronicAddress", 20 | "Jurisdiction", 21 | "Kind", 22 | "Icons", 23 | ]; 24 | Provider.create = (newProvider, Names, result) => { 25 | if (!Names || Names.length == 0) { 26 | result({ message: "Provider name required!" }, null); 27 | return; 28 | } 29 | for (const Name of Names) { 30 | if (Name.name == "") { 31 | result({ message: "Provider cannot be empty!" }, null); 32 | return; 33 | } 34 | } 35 | for (let field of jsonfields) { 36 | try { 37 | newProvider[field] = JSON.stringify(newProvider[field]); 38 | } catch (e) { 39 | log(err); 40 | } 41 | } 42 | sql.query( 43 | "INSERT INTO Organization(Kind,ContactName,Jurisdiction,Address,ElectronicAddress,Regulator,Icons) VALUES (?,?,?,?,?,?,?)", 44 | [ 45 | newProvider.Kind, 46 | newProvider.ContactName, 47 | newProvider.Jurisdiction, 48 | newProvider.Address, 49 | newProvider.ElectronicAddress, 50 | newProvider.Regulator, 51 | newProvider.Icons, 52 | ], 53 | (err, res) => { 54 | if (err) { 55 | log(err); 56 | result(err, null); 57 | return; 58 | } 59 | 60 | const orgId = res.insertId; 61 | 62 | for (const index in Names) { 63 | const data = { ...Names[index], organization: orgId }; 64 | 65 | sql.query("INSERT INTO EntityName SET ?", data, (err, res) => { 66 | if (err) { 67 | log(err); 68 | } 69 | }); 70 | } 71 | 72 | //Assume for now only one ServiceListRegistry, use id 1 73 | sql.query( 74 | "INSERT INTO ProviderOffering(Organization,ServiceListRegistry,updatedUtc) VALUES (?,?,utc_timestamp())", 75 | [orgId /*res.insertId*/, 1], 76 | (err, res) => { 77 | if (err) { 78 | log(err); 79 | result(err, null); 80 | return; 81 | } 82 | 83 | result(null, { id: res.insertId, ...newProvider }); 84 | } 85 | ); 86 | } 87 | ); 88 | }; 89 | 90 | Provider.findById = (ProviderId, result) => { 91 | sql.query( 92 | `SELECT ProviderOffering.Id,ProviderOffering.Organization,ProviderOffering.ServiceListRegistry,Organization.Kind,Organization.ContactName,Organization.Jurisdiction,Organization.Address,Organization.ElectronicAddress,Organization.Regulator,Organization.Icons,ProviderOffering.updatedUtc FROM ProviderOffering,Organization,EntityName WHERE ProviderOffering.Id = ? AND ProviderOffering.Organization = Organization.Id `, 93 | [ProviderId], 94 | async (err, res) => { 95 | if (err) { 96 | log(err); 97 | result(err, null); 98 | return; 99 | } 100 | 101 | if (res.length) { 102 | res[0].Regulator = res[0].Regulator != 0; 103 | 104 | // fetch names 105 | let provider = res[0]; 106 | for (let field of jsonfields) { 107 | try { 108 | provider[field] = JSON.parse(provider[field]); 109 | } catch (e) { 110 | provider[field] = {}; 111 | log(err); 112 | } 113 | } 114 | const names = await getNames(provider).catch((err) => { 115 | log(err); 116 | }); 117 | provider.Names = []; 118 | if (names) { 119 | names.forEach((n) => { 120 | provider.Names.push({ name: n.Name, type: n.Type }); 121 | }); 122 | } 123 | 124 | result(null, res[0]); 125 | return; 126 | } 127 | 128 | // not found Provider with the id 129 | result({ kind: "not_found" }, null); 130 | } 131 | ); 132 | }; 133 | 134 | Provider.getAll = (result) => { 135 | sql.query( 136 | "SELECT ProviderOffering.Id,ProviderOffering.Organization,ProviderOffering.ServiceListRegistry,Organization.Kind, Organization.ContactName,Organization.Jurisdiction,Organization.Address,Organization.ElectronicAddress,Organization.Regulator,Organization.Icons,ProviderOffering.updatedUtc FROM ProviderOffering,Organization where ProviderOffering.Organization = Organization.Id", 137 | async (err, res) => { 138 | if (err) { 139 | log(err); 140 | result(err, null); 141 | return; 142 | } 143 | 144 | try { 145 | for (let i = 0; i < res.length; i++) { 146 | let provider = res[i]; 147 | for (let field of jsonfields) { 148 | try { 149 | provider[field] = JSON.parse(provider[field]); 150 | } catch (e) { 151 | log(err); 152 | } 153 | } 154 | // Fetch Names 155 | const names = await getNames(provider).catch((err) => { 156 | log(err); 157 | }); 158 | provider.Names = []; 159 | if (names) { 160 | names.forEach((n) => { 161 | provider.Names.push({ name: n.Name, type: n.Type }); 162 | }); 163 | } 164 | } 165 | } catch (err) { 166 | log(err); 167 | result(err, null); 168 | return; 169 | } 170 | result(null, res); 171 | } 172 | ); 173 | }; 174 | 175 | Provider.updateById = (id, Provider, result) => { 176 | if (!Provider.Names || Provider.Names.length == 0) { 177 | result({ message: "Provider name required!" }, null); 178 | return; 179 | } 180 | for (const Name of Provider.Names) { 181 | if (Name.name == "") { 182 | result({ message: "Provider name cannot be empty!" }, null); 183 | return; 184 | } 185 | } 186 | sql.query( 187 | "UPDATE ProviderOffering SET updatedUtc = utc_timestamp() WHERE Id = ?", 188 | id 189 | ); 190 | 191 | sql.query( 192 | "SELECT Organization FROM ProviderOffering WHERE Id = ?", 193 | id, 194 | (err, res) => { 195 | if (err) { 196 | log(err); 197 | result(err, null); 198 | return; 199 | } 200 | const orgId = res[0].Organization; 201 | for (let field of jsonfields) { 202 | try { 203 | Provider[field] = JSON.stringify(Provider[field]); 204 | } catch (e) { 205 | result(e, null); 206 | log(e); 207 | return; 208 | } 209 | } 210 | sql.query( 211 | "UPDATE Organization SET Kind = ?, ContactName = ?, Jurisdiction = ?,Address = ?,ElectronicAddress = ?,Regulator = ?, Icons = ? WHERE Id = ?", 212 | [ 213 | Provider.Kind, 214 | Provider.ContactName, 215 | Provider.Jurisdiction, 216 | Provider.Address, 217 | Provider.ElectronicAddress, 218 | Provider.Regulator ? 1 : 0, 219 | Provider.Icons, 220 | res[0].Organization, 221 | ], 222 | async (err, res) => { 223 | if (err) { 224 | log(err); 225 | result(err, null); 226 | return; 227 | } 228 | // update names 229 | await removeNames(orgId); 230 | await createNames(orgId, Provider); 231 | result(null, { id: id, ...Provider }); 232 | } 233 | ); 234 | } 235 | ); 236 | }; 237 | 238 | Provider.remove = (id, result) => { 239 | sql.query( 240 | "SELECT Organization FROM ProviderOffering WHERE Id = ?", 241 | id, 242 | (err, res) => { 243 | if (err) { 244 | log(err); 245 | result(err, null); 246 | return; 247 | } 248 | //delete organization, cascade will remove the provideroffering 249 | const orgId = res[0].Organization; 250 | sql.query("DELETE FROM Organization WHERE id = ?", orgId, (err, res) => { 251 | if (err) { 252 | log(err); 253 | result(err, null); 254 | return; 255 | } 256 | 257 | if (res.affectedRows == 0) { 258 | // not found Provider with the id 259 | result({ kind: "not_found" }, null); 260 | return; 261 | } 262 | result(null, res); 263 | }); 264 | } 265 | ); 266 | }; 267 | 268 | Provider.removeAll = (result) => { 269 | sql.query("DELETE FROM Providers", (err, res) => { 270 | if (err) { 271 | log(err); 272 | result(err, null); 273 | return; 274 | } 275 | result(null, res); 276 | }); 277 | }; 278 | 279 | function removeNames(id) { 280 | return new Promise((resolve, reject) => { 281 | sql.query( 282 | `DELETE FROM EntityName where EntityName.Organization = ?`, 283 | [id], 284 | (err, res) => { 285 | if (err) { 286 | log(err); 287 | reject(err); 288 | } else { 289 | resolve(res); 290 | } 291 | } 292 | ); 293 | }); 294 | } 295 | 296 | async function createNames(orgId, provider) { 297 | let promises = []; 298 | for (const index in provider.Names) { 299 | const data = { ...provider.Names[index], organization: orgId }; 300 | // with promise.all the names are not always saved in the same order 301 | //promises.push( 302 | await new Promise((resolve, reject) => { 303 | sql.query("INSERT INTO EntityName SET ?", data, (err, res) => { 304 | if (err) { 305 | log(err); 306 | reject(); 307 | } 308 | resolve(); 309 | }); 310 | }).catch((err) => { 311 | return err; 312 | }); 313 | } 314 | } 315 | 316 | function getNames(provider) { 317 | return new Promise((resolve, reject) => { 318 | sql.query( 319 | `SELECT * FROM EntityName where EntityName.Organization = ?`, 320 | [provider.Organization], 321 | (err, res) => { 322 | if (err) { 323 | log(err); 324 | reject(err); 325 | } else { 326 | resolve(res); 327 | } 328 | } 329 | ); 330 | }); 331 | } 332 | 333 | Provider.findByName = (name) => { 334 | return new Promise((resolve, reject) => { 335 | sql.query( 336 | "SELECT ProviderOffering.Id FROM EntityName, ProviderOffering where EntityName.Name = ? and EntityName.Organization != 1 and EntityName.Organization = ProviderOffering.Organization", 337 | [name], 338 | (err, res) => { 339 | if (err) { 340 | log(err); 341 | reject(err); 342 | } else { 343 | resolve(res); 344 | } 345 | } 346 | ); 347 | }); 348 | }; 349 | 350 | module.exports = Provider; 351 | -------------------------------------------------------------------------------- /backend/app/models/user.model.js: -------------------------------------------------------------------------------- 1 | const { log } = require("../../logging.js"); 2 | const sql = require("./db.js"); 3 | 4 | const User = function (newuser) { 5 | this.Name = newuser.Name; 6 | this.Email = newuser.Email; 7 | this.Hash = newuser.passwordhash; 8 | this.Role = newuser.Role; 9 | this.Providers = newuser.Providers; 10 | this.Session = newuser.Session; 11 | }; 12 | 13 | User.create = (user) => { 14 | return new Promise((resolve, reject) => { 15 | sql.query("INSERT INTO User SET ?", user, (err, res) => { 16 | if (err) { 17 | log(err); 18 | reject(err); 19 | } else { 20 | resolve(res); 21 | } 22 | }); 23 | }); 24 | }; 25 | 26 | User.getAll = () => { 27 | return new Promise((resolve, reject) => { 28 | sql.query("SELECT * FROM User", (err, res) => { 29 | if (err) { 30 | log(err); 31 | reject(err); 32 | } else { 33 | // return everything but the pass hash 34 | res.forEach((element) => { 35 | delete element["Hash"]; 36 | element.Providers = JSON.parse(element.Providers); 37 | }); 38 | resolve(res); 39 | } 40 | }); 41 | }); 42 | }; 43 | 44 | // used by admin operations, remove user hash from response 45 | User.findById = (Id) => { 46 | return new Promise((resolve, reject) => { 47 | sql.query(`SELECT * FROM User WHERE User.Id = ?`, [Id], (err, res) => { 48 | if (err) { 49 | log(err); 50 | reject(err); 51 | } else { 52 | res.forEach((element) => { 53 | delete element["Hash"]; 54 | element.Providers = JSON.parse(element.Providers); 55 | }); 56 | resolve(res); 57 | } 58 | }); 59 | }); 60 | }; 61 | 62 | // used by auth, so will retrn hash 63 | User.findByName = (Name) => { 64 | return new Promise((resolve, reject) => { 65 | sql.query(`SELECT * FROM User WHERE User.Name = ?`, [Name], (err, res) => { 66 | if (err) { 67 | log(err); 68 | reject(err); 69 | } else { 70 | for (let i = 0; i < res.length; i++) { 71 | res[i].Providers = JSON.parse(res[i].Providers); 72 | } 73 | resolve(res); 74 | } 75 | }); 76 | }); 77 | }; 78 | 79 | 80 | User.getAllByProvider = async (provider, result) => { 81 | sql.query("SELECT User.Id, User.Name, User.Role, User.Providers, User.Email FROM User ", async (err, res) => { 82 | if (err) { 83 | log(err); 84 | result(err, null); 85 | return; 86 | } 87 | 88 | let list = []; 89 | for (let i = 0; i < res.length; i++) { 90 | res[i].Providers = JSON.parse(res[i].Providers); 91 | try { 92 | if (res[i].Providers.includes(provider)) { 93 | list.push(res[i]); 94 | } 95 | } catch(err) { 96 | log(err); 97 | } 98 | } 99 | 100 | result(null, list); 101 | }); 102 | }; 103 | 104 | User.updateById = (Id, admin, newUser, result) => { 105 | // verify needed data is not missing 106 | newUser.Name = newUser.Name || ""; 107 | newUser.Email = newUser.Email || ""; 108 | newUser.Role = newUser.Role || "user"; 109 | newUser.Providers = newUser.Providers || []; 110 | 111 | if (admin) { 112 | sql.query( 113 | "UPDATE User SET Name = ?, Email = ?, Role = ?, Providers = ? WHERE Id = ?", 114 | [newUser.Name, newUser.Email, newUser.Role, JSON.stringify(newUser.Providers), Id], 115 | async (err, res) => { 116 | if (err) { 117 | log(err); 118 | result(err, null); 119 | return; 120 | } 121 | result(null, { Id: Id }); 122 | } 123 | ); 124 | } else { 125 | // regular user can only edit these fields 126 | sql.query( 127 | "UPDATE User SET Email = ?, Providers = ? WHERE Id = ?", 128 | [newUser.Email,JSON.stringify( newUser.Providers ), Id], 129 | async (err, res) => { 130 | if (err) { 131 | log(err); 132 | result(err, null); 133 | return; 134 | } 135 | result(null, { Id: Id }); 136 | } 137 | ); 138 | } 139 | }; 140 | 141 | User.remove = async (id, result) => { 142 | sql.query("DELETE FROM User WHERE Id = ?", id, (err, res) => { 143 | if (err) { 144 | log(err); 145 | result(err, null); 146 | return; 147 | } 148 | 149 | if (res.affectedRows == 0) { 150 | result({ user: "not_found" }, null); 151 | return; 152 | } 153 | result(null, res); 154 | }); 155 | }; 156 | 157 | // updateSession 158 | User.updateSession = (Id, Session, result) => { 159 | sql.query("UPDATE User SET Session = ? WHERE Id = ?", [Session, Id], (err, res) => { 160 | if (err) { 161 | log(err); 162 | result(err, null); 163 | return; 164 | } 165 | 166 | result(null, { Id: Id }); 167 | }); 168 | }; 169 | 170 | User.updatePwd = (Id, Hash, result) => { 171 | sql.query("UPDATE User SET Hash = ? WHERE Id = ?", [Hash, Id], async (err, res) => { 172 | if (err) { 173 | log(err); 174 | result(err, null); 175 | return; 176 | } 177 | result(null, { Id: Id }); 178 | }); 179 | }; 180 | 181 | module.exports = User; 182 | -------------------------------------------------------------------------------- /backend/app/routes/eventhistory.routes.js: -------------------------------------------------------------------------------- 1 | module.exports = (app) => { 2 | const eventhistory = require("../controllers/eventhistory.controller.js"); 3 | 4 | // Retrieve all servicelists 5 | //app.get("/eventhistory", eventhistory.findAll); 6 | 7 | // Retrieve a single list events with listId 8 | app.get("/eventhistory/:listId", eventhistory.findOne); 9 | }; 10 | -------------------------------------------------------------------------------- /backend/app/routes/listprovider.routes.js: -------------------------------------------------------------------------------- 1 | module.exports = (app) => { 2 | const listprovider = require("../controllers/listprovider.controller.js"); 3 | 4 | // get listprovider 5 | app.get("/listprovider", listprovider.getProvider); 6 | 7 | // Update listProvider 8 | app.put("/listprovider", listprovider.update); 9 | }; 10 | -------------------------------------------------------------------------------- /backend/app/routes/provider.routes.js: -------------------------------------------------------------------------------- 1 | module.exports = (app) => { 2 | const providers = require("../controllers/provider.controller.js"); 3 | 4 | // Create a new provider 5 | app.post("/providers", providers.create); 6 | 7 | // Retrieve all providers 8 | app.get("/providers", providers.findAll); 9 | 10 | // Retrieve a single Customer with customerId 11 | app.get("/providers/:customerId", providers.findOne); 12 | 13 | // Update a Customer with customerId 14 | app.put("/providers/:customerId", providers.update); 15 | 16 | // Delete a Customer with customerId 17 | app.delete("/providers/:customerId", providers.delete); 18 | }; 19 | -------------------------------------------------------------------------------- /backend/app/routes/public.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/user.model.js"); 2 | const bcrypt = require("bcryptjs"); 3 | const jwt = require("jsonwebtoken"); 4 | const { log } = require("../../logging.js"); 5 | 6 | // public routes, no auth required 7 | 8 | module.exports = (app) => { 9 | app.get("/setup", async (req, res) => { 10 | try { 11 | const users = await User.getAll(); 12 | 13 | if (!users || users.length < 1) { 14 | // create first user 15 | const username = process.env.ADMIN_USERNAME || "admin"; 16 | const password = process.env.ADMIN_PASSWORD || "admin"; 17 | 18 | const passwordHash = await bcrypt.hash(password, 8); 19 | 20 | const user = new User({ 21 | Name: username, 22 | passwordhash: passwordHash, 23 | Role: "admin", 24 | Organization: 0, 25 | Providers: "", 26 | Session: 1, 27 | }); 28 | 29 | const newUser = await User.create(user); 30 | 31 | if (newUser) { 32 | return res.status(200).json({ success: true }); 33 | } else { 34 | return res.status(500).json({ success: false }); 35 | } 36 | } else { 37 | return res 38 | .status(400) 39 | .json({ success: false, error: "already created" }); 40 | } 41 | } catch (err) { 42 | log(err); 43 | return res.status(400).json({ success: false }); 44 | } 45 | }); 46 | 47 | app.post("/authenticate", async (req, res) => { 48 | try { 49 | let { 50 | body: { username, password }, 51 | } = req; 52 | 53 | const user = await User.findByName(username); 54 | 55 | if (!user || user.length < 1) { 56 | return res 57 | .status(401) 58 | .json({ success: false, error: "general error" }); 59 | } 60 | 61 | const { Id, Hash, Role, Session } = user[0]; 62 | 63 | const match = await bcrypt.compare(password, Hash); 64 | if (!match) { 65 | return res 66 | .status(401) 67 | .json({ success: false, error: "general error" }); 68 | } 69 | 70 | // create token 71 | // include user IP in token ? todo 72 | const token = jwt.sign( 73 | { Id, Role, Session }, 74 | req.app.get("jwtstring"), 75 | { expiresIn: "4h" } 76 | ); 77 | 78 | // log the login: "user logged in from ip" 79 | let user_data = { 80 | role: Role === "admin", 81 | }; 82 | return res.status(200).json({ success: true, token, user: user_data }); 83 | } catch (err) { 84 | log(err); 85 | return res.status(401).json({ success: false, error: "general error" }); 86 | } 87 | }); 88 | }; 89 | -------------------------------------------------------------------------------- /backend/app/routes/servicelist.routes.js: -------------------------------------------------------------------------------- 1 | module.exports = (app) => { 2 | const servicelist = require("../controllers/servicelist.controller.js"); 3 | 4 | // Create a new service list 5 | app.post("/servicelist", servicelist.create); 6 | 7 | // Retrieve all servicelists 8 | app.get("/servicelist", servicelist.findAll); 9 | 10 | // Retrieve a single list with listId 11 | app.get("/servicelist/:listId", servicelist.findOne); 12 | 13 | // Retrieve a single list with uniqueid 14 | app.get("/servicelist-id/:listId", servicelist.findById); 15 | 16 | // Update a list with listId 17 | app.put("/servicelist/:listId", servicelist.update); 18 | 19 | // Delete a list with listId 20 | app.delete("/servicelist/:listId", servicelist.delete); 21 | 22 | // Retrieve all lists per provider 23 | app.get("/servicelist/provider/:providerId", servicelist.findAllByProvider); 24 | }; 25 | -------------------------------------------------------------------------------- /backend/app/routes/user.routes.js: -------------------------------------------------------------------------------- 1 | module.exports = (app) => { 2 | const user = require("../controllers/user.controller.js"); 3 | 4 | // Retrieve all users 5 | app.get("/users", user.findAll); 6 | 7 | // Retrieve a single user 8 | app.get("/users/:userId", user.findOne); 9 | 10 | // Create a new user 11 | app.post("/users", user.create); 12 | 13 | // Update a user with userId 14 | app.put("/users/:userId", user.update); 15 | 16 | // Delete a user with userId 17 | app.delete("/users/:userId", user.delete); 18 | 19 | // log out a users token 20 | app.get("/logout", user.logout); 21 | 22 | // change password 23 | app.post("/pwd", user.changePassword); 24 | 25 | // Retrieve all per provider 26 | app.get("/users/provider/:providerId", user.findAllByProvider); 27 | }; 28 | -------------------------------------------------------------------------------- /backend/logging.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | let logger = null; 5 | 6 | 7 | const ERROR = "error" 8 | const LOG = "log" 9 | const WARN = "warn" 10 | const INFO = "info" 11 | 12 | const logs = { 13 | [LOG]: console.log, 14 | [WARN]: console.warn, 15 | [INFO]: console.info, 16 | [ERROR]: console.error 17 | } 18 | 19 | const graylog = { 20 | [LOG]: logger?.log, 21 | [WARN]: logger?.warning, 22 | [INFO]: logger?.info, 23 | [ERROR]: logger?.error 24 | } 25 | 26 | function log(...val) { 27 | logging(LOG,...val) 28 | } 29 | function warn(...val) { 30 | logging(WARN,...val) 31 | } 32 | function info(...val) { 33 | logging(INFO,...val) 34 | } 35 | function error(...val) { 36 | logging(ERROR,...val) 37 | } 38 | 39 | function logging(type,...val) { 40 | try { 41 | if(logger) { 42 | //TODO implement logging, graylog for example 43 | } 44 | else { 45 | logs[type].call(this,...val) 46 | } 47 | } 48 | catch(e) { 49 | console.error("Logging error",e,type,...val) 50 | } 51 | 52 | } 53 | 54 | module.exports = {log,warn,info,error} -------------------------------------------------------------------------------- /backend/middleware/authentication.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const User = require("../app/models/user.model.js"); 3 | 4 | // validate token and fetch user 5 | module.exports = async (req, res, next) => { 6 | try { 7 | let token = req.headers.authorization; 8 | if (!token) { 9 | return res.status(403).json({ 10 | success: false, 11 | }); 12 | } 13 | 14 | const { Id, Session } = jwt.verify(token, req.app.get("jwtstring")); 15 | if (!Id) { 16 | return res.status(403).json({ 17 | success: false, 18 | }); 19 | } 20 | 21 | const user = await User.findById(Id); 22 | 23 | if (!user || user.length < 1) { 24 | return res.status(403).json({ success: false }); 25 | } 26 | 27 | // check token session id vs users current active session 28 | if (!Session || Session != user[0].Session) { 29 | return res.status(403).json({ success: false }); 30 | } 31 | 32 | req.user = user[0]; 33 | 34 | return next(); 35 | } catch (err) { 36 | // expired token or other error 37 | return res.status(401).json({ 38 | success: false, 39 | }); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dvbi-csr-backend", 3 | "version": "1.0.0", 4 | "description": "DVB-I CSR Backend", 5 | "main": "server.js", 6 | "license": "MIT", 7 | "dependencies": { 8 | "bcryptjs": "^2.4.3", 9 | "cors": "^2.8.5", 10 | "dotenv": "^10.0.0", 11 | "express": "^4.17.1", 12 | "jsonwebtoken": "^9.0.2", 13 | "mysql2": "^3.9.7" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/server.js: -------------------------------------------------------------------------------- 1 | // catch all rogue exceptions 2 | process.on("uncaughtException", (err) => { 3 | error(err) 4 | }); 5 | 6 | require("dotenv").config(); 7 | const express = require("express"); 8 | 9 | const app = express(); 10 | const cors = require("cors"); 11 | const { error, info } = require("./logging"); 12 | 13 | const PORT = process.env.PORT || 3000; 14 | app.use(cors()); 15 | 16 | app.disable("x-powered-by"); 17 | if (!process.env.JWT_SECRET) { 18 | error("WARNING! JWT_SECRET NOT DEFINED! PLEASE DEFINE YOUR JWT SECRET IN THE ENV-FILE!"); 19 | } 20 | app.set("jwtstring", process.env.JWT_SECRET || "7öldÖJISjfs903jF(NljewOIWJRÖOA30SF"); 21 | 22 | // parse requests of content-type: application/json 23 | app.use(express.json({limit: '10mb'})); 24 | 25 | // parse requests of content-type: application/x-www-form-urlencoded 26 | app.use(express.urlencoded({ extended: true })); 27 | 28 | // simple route 29 | app.get("/", (req, res) => { 30 | res.json({ message: "DVB-I CSR backend" }); 31 | }); 32 | 33 | // public routes 34 | require("./app/routes/public")(app); 35 | 36 | // auth 37 | app.use(require("./middleware/authentication")); 38 | 39 | // service routes 40 | require("./app/routes/provider.routes")(app); 41 | require("./app/routes/servicelist.routes")(app); 42 | require("./app/routes/eventhistory.routes")(app); 43 | require("./app/routes/listprovider.routes")(app); 44 | require("./app/routes/user.routes")(app); 45 | 46 | // set port, listen for requests 47 | app.listen(PORT, () => { 48 | info("Backend server is running on port "+PORT); 49 | }); -------------------------------------------------------------------------------- /common/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | "env": { 4 | "es6": true 5 | } 6 | } -------------------------------------------------------------------------------- /common/countries.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* eslint-env es6 */ 3 | // https://raw.githubusercontent.com/lukes/ISO-3166-Countries-with-Regional-Codes/master/slim-3/slim-3.json 4 | 5 | const countries = { 6 | AFG: { name: "Afghanistan" }, 7 | ALA: { name: "Åland Islands" }, 8 | ALB: { name: "Albania" }, 9 | DZA: { name: "Algeria" }, 10 | ASM: { name: "American Samoa" }, 11 | AND: { name: "Andorra" }, 12 | AGO: { name: "Angola" }, 13 | AIA: { name: "Anguilla" }, 14 | ATA: { name: "Antarctica" }, 15 | ATG: { name: "Antigua and Barbuda" }, 16 | ARG: { name: "Argentina" }, 17 | ARM: { name: "Armenia" }, 18 | ABW: { name: "Aruba" }, 19 | AUS: { name: "Australia" }, 20 | AUT: { name: "Austria" }, 21 | AZE: { name: "Azerbaijan" }, 22 | BHS: { name: "Bahamas" }, 23 | BHR: { name: "Bahrain" }, 24 | BGD: { name: "Bangladesh" }, 25 | BRB: { name: "Barbados" }, 26 | BLR: { name: "Belarus" }, 27 | BEL: { name: "Belgium" }, 28 | BLZ: { name: "Belize" }, 29 | BEN: { name: "Benin" }, 30 | BMU: { name: "Bermuda" }, 31 | BTN: { name: "Bhutan" }, 32 | BOL: { name: "Bolivia (Plurinational State of)" }, 33 | BES: { name: "Bonaire, Sint Eustatius and Saba" }, 34 | BIH: { name: "Bosnia and Herzegovina" }, 35 | BWA: { name: "Botswana" }, 36 | BVT: { name: "Bouvet Island" }, 37 | BRA: { name: "Brazil" }, 38 | IOT: { name: "British Indian Ocean Territory" }, 39 | BRN: { name: "Brunei Darussalam" }, 40 | BGR: { name: "Bulgaria" }, 41 | BFA: { name: "Burkina Faso" }, 42 | BDI: { name: "Burundi" }, 43 | CPV: { name: "Cabo Verde" }, 44 | KHM: { name: "Cambodia" }, 45 | CMR: { name: "Cameroon" }, 46 | CAN: { name: "Canada" }, 47 | CYM: { name: "Cayman Islands" }, 48 | CAF: { name: "Central African Republic" }, 49 | TCD: { name: "Chad" }, 50 | CHL: { name: "Chile" }, 51 | CHN: { name: "China" }, 52 | CXR: { name: "Christmas Island" }, 53 | CCK: { name: "Cocos (Keeling) Islands" }, 54 | COL: { name: "Colombia" }, 55 | COM: { name: "Comoros" }, 56 | COG: { name: "Congo" }, 57 | COD: { name: "Congo, Democratic Republic of the" }, 58 | COK: { name: "Cook Islands" }, 59 | CRI: { name: "Costa Rica" }, 60 | CIV: { name: "Côte d'Ivoire" }, 61 | HRV: { name: "Croatia" }, 62 | CUB: { name: "Cuba" }, 63 | CUW: { name: "Curaçao" }, 64 | CYP: { name: "Cyprus" }, 65 | CZE: { name: "Czechia" }, 66 | DNK: { name: "Denmark" }, 67 | DJI: { name: "Djibouti" }, 68 | DMA: { name: "Dominica" }, 69 | DOM: { name: "Dominican Republic" }, 70 | ECU: { name: "Ecuador" }, 71 | EGY: { name: "Egypt" }, 72 | SLV: { name: "El Salvador" }, 73 | GNQ: { name: "Equatorial Guinea" }, 74 | ERI: { name: "Eritrea" }, 75 | EST: { name: "Estonia" }, 76 | SWZ: { name: "Eswatini" }, 77 | ETH: { name: "Ethiopia" }, 78 | FLK: { name: "Falkland Islands (Malvinas)" }, 79 | FRO: { name: "Faroe Islands" }, 80 | FJI: { name: "Fiji" }, 81 | FIN: { name: "Finland" }, 82 | FRA: { name: "France" }, 83 | GUF: { name: "French Guiana" }, 84 | PYF: { name: "French Polynesia" }, 85 | ATF: { name: "French Southern Territories" }, 86 | GAB: { name: "Gabon" }, 87 | GMB: { name: "Gambia" }, 88 | GEO: { name: "Georgia" }, 89 | DEU: { name: "Germany" }, 90 | GHA: { name: "Ghana" }, 91 | GIB: { name: "Gibraltar" }, 92 | GRC: { name: "Greece" }, 93 | GRL: { name: "Greenland" }, 94 | GRD: { name: "Grenada" }, 95 | GLP: { name: "Guadeloupe" }, 96 | GUM: { name: "Guam" }, 97 | GTM: { name: "Guatemala" }, 98 | GGY: { name: "Guernsey" }, 99 | GIN: { name: "Guinea" }, 100 | GNB: { name: "Guinea-Bissau" }, 101 | GUY: { name: "Guyana" }, 102 | HTI: { name: "Haiti" }, 103 | HMD: { name: "Heard Island and McDonald Islands" }, 104 | VAT: { name: "Holy See" }, 105 | HND: { name: "Honduras" }, 106 | HKG: { name: "Hong Kong" }, 107 | HUN: { name: "Hungary" }, 108 | ISL: { name: "Iceland" }, 109 | IND: { name: "India" }, 110 | IDN: { name: "Indonesia" }, 111 | IRN: { name: "Iran (Islamic Republic of)" }, 112 | IRQ: { name: "Iraq" }, 113 | IRL: { name: "Ireland" }, 114 | IMN: { name: "Isle of Man" }, 115 | ISR: { name: "Israel" }, 116 | ITA: { name: "Italy" }, 117 | JAM: { name: "Jamaica" }, 118 | JPN: { name: "Japan" }, 119 | JEY: { name: "Jersey" }, 120 | JOR: { name: "Jordan" }, 121 | KAZ: { name: "Kazakhstan" }, 122 | KEN: { name: "Kenya" }, 123 | KIR: { name: "Kiribati" }, 124 | PRK: { name: "Korea (Democratic People's Republic of)" }, 125 | KOR: { name: "Korea, Republic of" }, 126 | KWT: { name: "Kuwait" }, 127 | KGZ: { name: "Kyrgyzstan" }, 128 | LAO: { name: "Lao People's Democratic Republic" }, 129 | LVA: { name: "Latvia" }, 130 | LBN: { name: "Lebanon" }, 131 | LSO: { name: "Lesotho" }, 132 | LBR: { name: "Liberia" }, 133 | LBY: { name: "Libya" }, 134 | LIE: { name: "Liechtenstein" }, 135 | LTU: { name: "Lithuania" }, 136 | LUX: { name: "Luxembourg" }, 137 | MAC: { name: "Macao" }, 138 | MDG: { name: "Madagascar" }, 139 | MWI: { name: "Malawi" }, 140 | MYS: { name: "Malaysia" }, 141 | MDV: { name: "Maldives" }, 142 | MLI: { name: "Mali" }, 143 | MLT: { name: "Malta" }, 144 | MHL: { name: "Marshall Islands" }, 145 | MTQ: { name: "Martinique" }, 146 | MRT: { name: "Mauritania" }, 147 | MUS: { name: "Mauritius" }, 148 | MYT: { name: "Mayotte" }, 149 | MEX: { name: "Mexico" }, 150 | FSM: { name: "Micronesia (Federated States of)" }, 151 | MDA: { name: "Moldova, Republic of" }, 152 | MCO: { name: "Monaco" }, 153 | MNG: { name: "Mongolia" }, 154 | MNE: { name: "Montenegro" }, 155 | MSR: { name: "Montserrat" }, 156 | MAR: { name: "Morocco" }, 157 | MOZ: { name: "Mozambique" }, 158 | MMR: { name: "Myanmar" }, 159 | NAM: { name: "Namibia" }, 160 | NRU: { name: "Nauru" }, 161 | NPL: { name: "Nepal" }, 162 | NLD: { name: "Netherlands" }, 163 | NCL: { name: "New Caledonia" }, 164 | NZL: { name: "New Zealand" }, 165 | NIC: { name: "Nicaragua" }, 166 | NER: { name: "Niger" }, 167 | NGA: { name: "Nigeria" }, 168 | NIU: { name: "Niue" }, 169 | NFK: { name: "Norfolk Island" }, 170 | MKD: { name: "North Macedonia" }, 171 | MNP: { name: "Northern Mariana Islands" }, 172 | NOR: { name: "Norway" }, 173 | OMN: { name: "Oman" }, 174 | PAK: { name: "Pakistan" }, 175 | PLW: { name: "Palau" }, 176 | PSE: { name: "Palestine, State of" }, 177 | PAN: { name: "Panama" }, 178 | PNG: { name: "Papua New Guinea" }, 179 | PRY: { name: "Paraguay" }, 180 | PER: { name: "Peru" }, 181 | PHL: { name: "Philippines" }, 182 | PCN: { name: "Pitcairn" }, 183 | POL: { name: "Poland" }, 184 | PRT: { name: "Portugal" }, 185 | PRI: { name: "Puerto Rico" }, 186 | QAT: { name: "Qatar" }, 187 | REU: { name: "Réunion" }, 188 | ROU: { name: "Romania" }, 189 | RUS: { name: "Russian Federation" }, 190 | RWA: { name: "Rwanda" }, 191 | BLM: { name: "Saint Barthélemy" }, 192 | SHN: { name: "Saint Helena, Ascension and Tristan da Cunha" }, 193 | KNA: { name: "Saint Kitts and Nevis" }, 194 | LCA: { name: "Saint Lucia" }, 195 | MAF: { name: "Saint Martin (French part)" }, 196 | SPM: { name: "Saint Pierre and Miquelon" }, 197 | VCT: { name: "Saint Vincent and the Grenadines" }, 198 | WSM: { name: "Samoa" }, 199 | SMR: { name: "San Marino" }, 200 | STP: { name: "Sao Tome and Principe" }, 201 | SAU: { name: "Saudi Arabia" }, 202 | SEN: { name: "Senegal" }, 203 | SRB: { name: "Serbia" }, 204 | SYC: { name: "Seychelles" }, 205 | SLE: { name: "Sierra Leone" }, 206 | SGP: { name: "Singapore" }, 207 | SXM: { name: "Sint Maarten (Dutch part)" }, 208 | SVK: { name: "Slovakia" }, 209 | SVN: { name: "Slovenia" }, 210 | SLB: { name: "Solomon Islands" }, 211 | SOM: { name: "Somalia" }, 212 | ZAF: { name: "South Africa" }, 213 | SGS: { name: "South Georgia and the South Sandwich Islands" }, 214 | SSD: { name: "South Sudan" }, 215 | ESP: { name: "Spain" }, 216 | LKA: { name: "Sri Lanka" }, 217 | SDN: { name: "Sudan" }, 218 | SUR: { name: "Suriname" }, 219 | SJM: { name: "Svalbard and Jan Mayen" }, 220 | SWE: { name: "Sweden" }, 221 | CHE: { name: "Switzerland" }, 222 | SYR: { name: "Syrian Arab Republic" }, 223 | TWN: { name: "Taiwan, Province of China" }, 224 | TJK: { name: "Tajikistan" }, 225 | TZA: { name: "Tanzania, United Republic of" }, 226 | THA: { name: "Thailand" }, 227 | TLS: { name: "Timor-Leste" }, 228 | TGO: { name: "Togo" }, 229 | TKL: { name: "Tokelau" }, 230 | TON: { name: "Tonga" }, 231 | TTO: { name: "Trinidad and Tobago" }, 232 | TUN: { name: "Tunisia" }, 233 | TUR: { name: "Turkey" }, 234 | TKM: { name: "Turkmenistan" }, 235 | TCA: { name: "Turks and Caicos Islands" }, 236 | TUV: { name: "Tuvalu" }, 237 | UGA: { name: "Uganda" }, 238 | UKR: { name: "Ukraine" }, 239 | ARE: { name: "United Arab Emirates" }, 240 | GBR: { name: "United Kingdom of Great Britain and Northern Ireland" }, 241 | USA: { name: "United States of America" }, 242 | UMI: { name: "United States Minor Outlying Islands" }, 243 | URY: { name: "Uruguay" }, 244 | UZB: { name: "Uzbekistan" }, 245 | VUT: { name: "Vanuatu" }, 246 | VEN: { name: "Venezuela (Bolivarian Republic of)" }, 247 | VNM: { name: "Viet Nam" }, 248 | VGB: { name: "Virgin Islands (British)" }, 249 | VIR: { name: "Virgin Islands (U.S.)" }, 250 | WLF: { name: "Wallis and Futuna" }, 251 | ESH: { name: "Western Sahara" }, 252 | YEM: { name: "Yemen" }, 253 | ZMB: { name: "Zambia" }, 254 | ZWE: { name: "Zimbabwe" }, 255 | }; 256 | 257 | 258 | module.exports = { countries} -------------------------------------------------------------------------------- /common/languages.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* eslint-env es6 */ 3 | // https://github.com/haliaeetus/iso-639/blob/master/data/iso_639-2.json 4 | 5 | const languages = { 6 | aa: { name: "Afar" }, 7 | ab: { name: "Abkhazian" }, 8 | af: { name: "Afrikaans" }, 9 | ak: { name: "Akan" }, 10 | sq: { name: "Albanian" }, 11 | am: { name: "Amharic" }, 12 | ar: { name: "Arabic" }, 13 | an: { name: "Aragonese" }, 14 | hy: { name: "Armenian" }, 15 | as: { name: "Assamese" }, 16 | av: { name: "Avaric" }, 17 | ae: { name: "Avestan" }, 18 | ay: { name: "Aymara" }, 19 | az: { name: "Azerbaijani" }, 20 | ba: { name: "Bashkir" }, 21 | bm: { name: "Bambara" }, 22 | eu: { name: "Basque" }, 23 | be: { name: "Belarusian" }, 24 | bn: { name: "Bengali" }, 25 | bh: { name: "Bihari languages" }, 26 | bi: { name: "Bislama" }, 27 | bo: { name: "Tibetan" }, 28 | bs: { name: "Bosnian" }, 29 | br: { name: "Breton" }, 30 | bg: { name: "Bulgarian" }, 31 | my: { name: "Burmese" }, 32 | ca: { name: "Catalan" }, 33 | cs: { name: "Czech" }, 34 | ch: { name: "Chamorro" }, 35 | ce: { name: "Chechen" }, 36 | zh: { name: "Chinese" }, 37 | cu: { name: "Church Slavic" }, 38 | cv: { name: "Chuvash" }, 39 | kw: { name: "Cornish" }, 40 | co: { name: "Corsican" }, 41 | cr: { name: "Cree" }, 42 | cy: { name: "Welsh" }, 43 | da: { name: "Danish" }, 44 | de: { name: "German" }, 45 | dv: { name: "Divehi" }, 46 | nl: { name: "Dutch" }, 47 | dz: { name: "Dzongkha" }, 48 | el: { name: "Greek, Modern (1453-)" }, 49 | en: { name: "English" }, 50 | eo: { name: "Esperanto" }, 51 | et: { name: "Estonian" }, 52 | ee: { name: "Ewe" }, 53 | fo: { name: "Faroese" }, 54 | fa: { name: "Persian" }, 55 | fj: { name: "Fijian" }, 56 | fi: { name: "Finnish" }, 57 | fr: { name: "French" }, 58 | fy: { name: "Western Frisian" }, 59 | ff: { name: "Fulah" }, 60 | ka: { name: "Georgian" }, 61 | gd: { name: "Gaelic" }, 62 | ga: { name: "Irish" }, 63 | gl: { name: "Galician" }, 64 | gv: { name: "Manx" }, 65 | gn: { name: "Guarani" }, 66 | gu: { name: "Gujarati" }, 67 | ht: { name: "Haitian" }, 68 | ha: { name: "Hausa" }, 69 | he: { name: "Hebrew" }, 70 | hz: { name: "Herero" }, 71 | hi: { name: "Hindi" }, 72 | ho: { name: "Hiri Motu" }, 73 | hr: { name: "Croatian" }, 74 | hu: { name: "Hungarian" }, 75 | ig: { name: "Igbo" }, 76 | is: { name: "Icelandic" }, 77 | io: { name: "Ido" }, 78 | ii: { name: "Sichuan Yi" }, 79 | iu: { name: "Inuktitut" }, 80 | ie: { name: "Interlingue" }, 81 | ia: { name: "Interlingua (International Auxiliary Language Association)" }, 82 | id: { name: "Indonesian" }, 83 | ik: { name: "Inupiaq" }, 84 | it: { name: "Italian" }, 85 | jv: { name: "Javanese" }, 86 | ja: { name: "Japanese" }, 87 | kl: { name: "Kalaallisut" }, 88 | kn: { name: "Kannada" }, 89 | ks: { name: "Kashmiri" }, 90 | kr: { name: "Kanuri" }, 91 | kk: { name: "Kazakh" }, 92 | km: { name: "Central Khmer" }, 93 | ki: { name: "Kikuyu" }, 94 | rw: { name: "Kinyarwanda" }, 95 | ky: { name: "Kirghiz" }, 96 | kv: { name: "Komi" }, 97 | kg: { name: "Kongo" }, 98 | ko: { name: "Korean" }, 99 | kj: { name: "Kuanyama" }, 100 | ku: { name: "Kurdish" }, 101 | lo: { name: "Lao" }, 102 | la: { name: "Latin" }, 103 | lv: { name: "Latvian" }, 104 | li: { name: "Limburgan" }, 105 | ln: { name: "Lingala" }, 106 | lt: { name: "Lithuanian" }, 107 | lb: { name: "Luxembourgish" }, 108 | lu: { name: "Luba-Katanga" }, 109 | lg: { name: "Ganda" }, 110 | mk: { name: "Macedonian" }, 111 | mh: { name: "Marshallese" }, 112 | ml: { name: "Malayalam" }, 113 | mi: { name: "Maori" }, 114 | mr: { name: "Marathi" }, 115 | ms: { name: "Malay" }, 116 | mg: { name: "Malagasy" }, 117 | mt: { name: "Maltese" }, 118 | mn: { name: "Mongolian" }, 119 | na: { name: "Nauru" }, 120 | nv: { name: "Navajo" }, 121 | nr: { name: "Ndebele, South" }, 122 | nd: { name: "Ndebele, North" }, 123 | ng: { name: "Ndonga" }, 124 | ne: { name: "Nepali" }, 125 | nn: { name: "Norwegian Nynorsk" }, 126 | nb: { name: "Bokmål, Norwegian" }, 127 | no: { name: "Norwegian" }, 128 | ny: { name: "Chichewa" }, 129 | oc: { name: "Occitan (post 1500)" }, 130 | oj: { name: "Ojibwa" }, 131 | or: { name: "Oriya" }, 132 | om: { name: "Oromo" }, 133 | os: { name: "Ossetian" }, 134 | pa: { name: "Panjabi" }, 135 | pi: { name: "Pali" }, 136 | pl: { name: "Polish" }, 137 | pt: { name: "Portuguese" }, 138 | ps: { name: "Pushto" }, 139 | qu: { name: "Quechua" }, 140 | rm: { name: "Romansh" }, 141 | ro: { name: "Romanian" }, 142 | rn: { name: "Rundi" }, 143 | ru: { name: "Russian" }, 144 | sg: { name: "Sango" }, 145 | sa: { name: "Sanskrit" }, 146 | si: { name: "Sinhala" }, 147 | sk: { name: "Slovak" }, 148 | sl: { name: "Slovenian" }, 149 | se: { name: "Northern Sami" }, 150 | sm: { name: "Samoan" }, 151 | sn: { name: "Shona" }, 152 | sd: { name: "Sindhi" }, 153 | so: { name: "Somali" }, 154 | st: { name: "Sotho, Southern" }, 155 | es: { name: "Spanish" }, 156 | sc: { name: "Sardinian" }, 157 | sr: { name: "Serbian" }, 158 | ss: { name: "Swati" }, 159 | su: { name: "Sundanese" }, 160 | sw: { name: "Swahili" }, 161 | sv: { name: "Swedish" }, 162 | ty: { name: "Tahitian" }, 163 | ta: { name: "Tamil" }, 164 | tt: { name: "Tatar" }, 165 | te: { name: "Telugu" }, 166 | tg: { name: "Tajik" }, 167 | tl: { name: "Tagalog" }, 168 | th: { name: "Thai" }, 169 | ti: { name: "Tigrinya" }, 170 | to: { name: "Tonga (Tonga Islands)" }, 171 | tn: { name: "Tswana" }, 172 | ts: { name: "Tsonga" }, 173 | tk: { name: "Turkmen" }, 174 | tr: { name: "Turkish" }, 175 | tw: { name: "Twi" }, 176 | ug: { name: "Uighur" }, 177 | uk: { name: "Ukrainian" }, 178 | ur: { name: "Urdu" }, 179 | uz: { name: "Uzbek" }, 180 | ve: { name: "Venda" }, 181 | vi: { name: "Vietnamese" }, 182 | vo: { name: "Volapük" }, 183 | wa: { name: "Walloon" }, 184 | wo: { name: "Wolof" }, 185 | xh: { name: "Xhosa" }, 186 | yi: { name: "Yiddish" }, 187 | yo: { name: "Yoruba" }, 188 | za: { name: "Zhuang" }, 189 | zu: { name: "Zulu" }, 190 | }; 191 | 192 | 193 | module.exports = { languages} 194 | -------------------------------------------------------------------------------- /db.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE dvb_i_csr DEFAULT CHARACTER SET utf8 COLLATE utf8_swedish_ci; 2 | USE dvb_i_csr; 3 | 4 | CREATE TABLE `Organization` 5 | ( 6 | `Kind` text NULL , 7 | `ContactName` text NULL , 8 | `Jurisdiction` text NULL , 9 | `Address` text NULL , 10 | `ElectronicAddress` text NULL , 11 | `Regulator` tinyint NOT NULL , 12 | `Id` integer NOT NULL AUTO_INCREMENT, 13 | `Icons` text NULL , 14 | 15 | PRIMARY KEY (`Id`) 16 | ) ENGINE=InnoDB; 17 | 18 | CREATE TABLE `EntityName` 19 | ( 20 | `Name` text NOT NULL , 21 | `Organization` integer NOT NULL , 22 | `Type` text NOT NULL , 23 | `Id` integer NOT NULL AUTO_INCREMENT, 24 | 25 | PRIMARY KEY (`Id`), 26 | KEY `fkIdx_21` (`Organization`), 27 | CONSTRAINT `FK_19` FOREIGN KEY `fkIdx_21` (`Organization`) REFERENCES `Organization` (`Id`) 28 | ) ENGINE=InnoDB; 29 | 30 | CREATE TABLE `ServiceListEntryPoints` 31 | ( 32 | `Id` integer NOT NULL AUTO_INCREMENT, 33 | `ServiceListRegistryEntity` integer NOT NULL , 34 | `Language` text NOT NULL, 35 | 36 | PRIMARY KEY (`Id`), 37 | KEY `fkIdx_46` (`ServiceListRegistryEntity`), 38 | CONSTRAINT `FK_44` FOREIGN KEY `fkIdx_46` (`ServiceListRegistryEntity`) REFERENCES `Organization` (`Id`) 39 | ) ENGINE=InnoDB; 40 | 41 | 42 | CREATE TABLE `ProviderOffering` 43 | ( 44 | `Id` integer NOT NULL AUTO_INCREMENT, 45 | `Organization` integer NOT NULL , 46 | `ServiceListRegistry` integer NOT NULL , 47 | 48 | PRIMARY KEY (`Id`), 49 | KEY `fkIdx_49` (`Organization`), 50 | CONSTRAINT `FK_47` FOREIGN KEY `fkIdx_49` (`Organization`) REFERENCES `Organization` (`Id`), 51 | KEY `fkIdx_58` (`ServiceListRegistry`), 52 | CONSTRAINT `FK_56` FOREIGN KEY `fkIdx_58` (`ServiceListRegistry`) REFERENCES `ServiceListEntryPoints` (`Id`) 53 | ) ENGINE=InnoDB; 54 | 55 | CREATE TABLE `ServiceListOffering` 56 | ( 57 | `Provider` integer NOT NULL , 58 | `regulatorList` tinyint NOT NULL , 59 | `Delivery` text NOT NULL , 60 | `Status` text NOT NULL , 61 | `Id` integer NOT NULL AUTO_INCREMENT , 62 | 63 | PRIMARY KEY (`Id`), 64 | KEY `fkIdx_55` (`Provider`), 65 | CONSTRAINT `FK_53` FOREIGN KEY `fkIdx_55` (`Provider`) REFERENCES `ProviderOffering` (`Id`) 66 | ) ENGINE=InnoDB; 67 | 68 | CREATE TABLE `Genre` 69 | ( 70 | `Genre` text NOT NULL , 71 | `ServiceList` integer NOT NULL , 72 | `Id` integer NOT NULL AUTO_INCREMENT, 73 | 74 | PRIMARY KEY (`Id`), 75 | KEY `fkIdx_136` (`ServiceList`), 76 | CONSTRAINT `FK_134` FOREIGN KEY `fkIdx_136` (`ServiceList`) REFERENCES `ServiceListOffering` (`Id`) 77 | ) ENGINE=InnoDB; 78 | 79 | CREATE TABLE `Language` 80 | ( 81 | `Language` text NOT NULL , 82 | `ServiceList` integer NOT NULL , 83 | `Id` integer NOT NULL AUTO_INCREMENT, 84 | 85 | PRIMARY KEY (`Id`), 86 | KEY `fkIdx_176` (`ServiceList`), 87 | CONSTRAINT `FK_174` FOREIGN KEY `fkIdx_176` (`ServiceList`) REFERENCES `ServiceListOffering` (`Id`) 88 | ) ENGINE=InnoDB; 89 | 90 | CREATE TABLE `ServiceListName` 91 | ( 92 | `ServiceList` integer NOT NULL , 93 | `Name` text NOT NULL , 94 | `Lang` text NOT NULL , 95 | `Id` integer NOT NULL AUTO_INCREMENT, 96 | 97 | PRIMARY KEY (`Id`), 98 | KEY `fkIdx_75` (`ServiceList`), 99 | CONSTRAINT `FK_73` FOREIGN KEY `fkIdx_75` (`ServiceList`) REFERENCES `ServiceListOffering` (`Id`) 100 | ) ENGINE=InnoDB; 101 | 102 | CREATE TABLE `ServiceListURI` 103 | ( 104 | `URI` text NOT NULL , 105 | `ServiceList` integer NOT NULL , 106 | `Id` integer NOT NULL AUTO_INCREMENT, 107 | 108 | PRIMARY KEY (`Id`), 109 | KEY `fkIdx_69` (`ServiceList`), 110 | CONSTRAINT `FK_67` FOREIGN KEY `fkIdx_69` (`ServiceList`) REFERENCES `ServiceListOffering` (`Id`) 111 | ) ENGINE=InnoDB; 112 | 113 | CREATE TABLE `TargetCountry` 114 | ( 115 | `ServiceList` integer NOT NULL , 116 | `Country` text NOT NULL , 117 | `Id` integer NOT NULL AUTO_INCREMENT, 118 | 119 | PRIMARY KEY (`Id`), 120 | KEY `fkIdx_118` (`ServiceList`), 121 | CONSTRAINT `FK_116` FOREIGN KEY `fkIdx_118` (`ServiceList`) REFERENCES `ServiceListOffering` (`Id`) 122 | ) ENGINE=InnoDB; 123 | 124 | CREATE TABLE `User` 125 | ( 126 | `Name` text NOT NULL , 127 | `Hash` text NOT NULL , 128 | `Role` text NOT NULL , 129 | `Providers` text NOT NULL , 130 | `Email` text NOT NULL , 131 | `Session` integer NOT NULL , 132 | `Id` integer NOT NULL AUTO_INCREMENT, 133 | 134 | PRIMARY KEY (`Id`) 135 | ) ENGINE=InnoDB; 136 | 137 | CREATE TABLE `EventHistory` 138 | ( 139 | `Event` text NOT NULL , 140 | `ServiceList` integer NOT NULL , 141 | `Name` text NOT NULL , 142 | `Time` text NOT NULL , 143 | `User` integer NOT NULL , 144 | `UserName` text NOT NULL , 145 | `ContentJson` text NOT NULL , 146 | `Id` integer NOT NULL AUTO_INCREMENT, 147 | 148 | PRIMARY KEY (`Id`) 149 | ) ENGINE=InnoDB; 150 | 151 | 152 | ALTER TABLE `EntityName` 153 | DROP FOREIGN KEY `FK_19`; 154 | 155 | ALTER TABLE `EntityName` 156 | ADD CONSTRAINT `FK_19` FOREIGN KEY `fkIdx_21` (`Organization`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE; 157 | 158 | ALTER TABLE `ServiceListEntryPoints` 159 | DROP FOREIGN KEY `FK_44`; 160 | 161 | ALTER TABLE `ServiceListEntryPoints` 162 | ADD CONSTRAINT `FK_44` FOREIGN KEY `fkIdx_46` (`ServiceListRegistryEntity`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE; 163 | 164 | ALTER TABLE `ProviderOffering` 165 | DROP FOREIGN KEY `FK_47`; 166 | 167 | ALTER TABLE `ProviderOffering` 168 | ADD CONSTRAINT `FK_47` FOREIGN KEY `fkIdx_49` (`Organization`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE; 169 | 170 | ALTER TABLE `ServiceListOffering` 171 | DROP FOREIGN KEY `FK_53`; 172 | 173 | ALTER TABLE `ServiceListOffering` 174 | ADD CONSTRAINT `FK_53` FOREIGN KEY `fkIdx_55` (`Provider`) REFERENCES `ProviderOffering` (`Id`) ON DELETE CASCADE; 175 | 176 | ALTER TABLE `Genre` 177 | DROP FOREIGN KEY `FK_134`; 178 | 179 | ALTER TABLE `Genre` 180 | ADD CONSTRAINT `FK_134` FOREIGN KEY `fkIdx_136` (`ServiceList`) REFERENCES `ServiceListOffering` (`Id`) ON DELETE CASCADE; 181 | 182 | ALTER TABLE `Language` 183 | DROP FOREIGN KEY `FK_174`; 184 | 185 | ALTER TABLE `Language` 186 | ADD CONSTRAINT `FK_174` FOREIGN KEY `fkIdx_176` (`ServiceList`) REFERENCES `ServiceListOffering` (`Id`) ON DELETE CASCADE; 187 | 188 | ALTER TABLE `ServiceListURI` 189 | DROP FOREIGN KEY `FK_67`; 190 | 191 | ALTER TABLE `ServiceListURI` 192 | ADD CONSTRAINT `FK_67` FOREIGN KEY `fkIdx_69` (`ServiceList`) REFERENCES `ServiceListOffering` (`Id`) ON DELETE CASCADE; 193 | 194 | ALTER TABLE `TargetCountry` 195 | DROP FOREIGN KEY `FK_116`; 196 | 197 | ALTER TABLE `TargetCountry` 198 | ADD CONSTRAINT `FK_116` FOREIGN KEY `fkIdx_118` (`ServiceList`) REFERENCES `ServiceListOffering` (`Id`) ON DELETE CASCADE; 199 | 200 | ALTER TABLE `ServiceListName` 201 | DROP FOREIGN KEY `FK_73`; 202 | 203 | ALTER TABLE `ServiceListName` 204 | ADD CONSTRAINT `FK_73` FOREIGN KEY `fkIdx_75` (`ServiceList`) REFERENCES `ServiceListOffering` (`Id`) ON DELETE CASCADE; 205 | 206 | /*alter table to migrate from older versions without dropping database*/ 207 | ALTER TABLE `ServiceListOffering` 208 | ADD `Status` text NOT NULL; 209 | 210 | ALTER TABLE `User` 211 | ADD `Providers` text NOT NULL; 212 | 213 | ALTER TABLE `User` 214 | DROP COLUMN `Organizations`; 215 | 216 | ALTER TABLE `User` 217 | ADD `Organization` TEXT NOT NULL; 218 | 219 | ALTER TABLE `User` 220 | ADD `Email` TEXT NOT NULL; 221 | 222 | ALTER TABLE `User` 223 | ADD `Session` integer NOT NULL; 224 | 225 | ALTER TABLE `User` 226 | DROP `Organization`; 227 | 228 | ALTER TABLE `ServiceListEntryPoints` 229 | ADD `Language` TEXT NOT NULL; 230 | 231 | ALTER TABLE `Organization` 232 | ADD `Icons` TEXT NOT NULL; 233 | 234 | INSERT INTO Organization(Kind,ContactName,Jurisdiction,Address,ElectronicAddress,Regulator,Id,Icons) VALUES ('[]','[]','{"Name": "","AddressLine": ["","",""]}','{"Name": "","AddressLine": ["","",""]}','{"Telephone": "","Fax": "","Email":"","Url": ""},',1,1,'[]'); 235 | INSERT INTO EntityName(Name,Type,Organization,Id) VALUES("Repository provider","",1,1); 236 | INSERT INTO ServiceListEntryPoints(ServiceListRegistryEntity,Id,Language) VALUES (1,1,"en"); 237 | 238 | INSERT INTO User(Name,Hash,Role,Id,Providers,Email,Session) VALUES ('admin','$2a$08$B5kXMji7bHC8yOO1xIqeO.Vy3oPc.rkQUTG4bNG1hZWNBmcz9eaZe','admin',1,"[]","",1); 239 | 240 | 241 | ALTER TABLE `ServiceListOffering` ADD `updatedUtc` datetime NOT NULL DEFAULT utc_timestamp(); 242 | ALTER TABLE `ProviderOffering` ADD `updatedUtc` datetime NOT NULL DEFAULT utc_timestamp(); 243 | 244 | ALTER TABLE `ServiceListOffering` 245 | ADD `Icons` TEXT NOT NULL; 246 | 247 | ALTER TABLE `ServiceListOffering` 248 | ADD `ServiceListId` text NOT NULL; 249 | -------------------------------------------------------------------------------- /docs/dvb-csr: -------------------------------------------------------------------------------- 1 | A Central Service List Registry (CSR), operated for the benefit of all devices implementing the DVB-I client, 2 | providing information on a wide set of service lists known to that registry. 3 | 4 | A DVB-I Service List Registry (SLR) is an HTTP endpoint available at a known URL that, if queried, can return a list 5 | of Service List Entry Points. A DVB-I Service List Provider who wishes to enable the SLR discovery mechanism for 6 | their own Service Lists may register their Service List Entry Points in the SLR using the M interface in clause 4.1. The 7 | SLR also collects contact information of the Service List Providers. 8 | 9 | (a177r2 page 18) 10 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | ## DVB CSR ## 2 | -------------------------------------------------------------------------------- /frontend/.env: -------------------------------------------------------------------------------- 1 | VITE_APP_BACKEND_URL=http://localhost:3000 -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DVB-I CSR 9 | 10 | 11 | 12 | 13 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dvbi-csr", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "serve": "vite --host", 8 | "build": "vite build" 9 | }, 10 | "dependencies": { 11 | "@popperjs/core": "^2.10.1", 12 | "axios": "^1.6.8", 13 | "bootstrap": "^5.1.1", 14 | "core-js": "^3.6.5", 15 | "vue": "^3.4.21", 16 | "vue-router": "4" 17 | }, 18 | "devDependencies": { 19 | "@vitejs/plugin-vue": "^5.0.4", 20 | "@vue/compiler-sfc": "^3.0.0", 21 | "babel-eslint": "^10.1.0", 22 | "eslint": "^6.7.2", 23 | "eslint-plugin-vue": "^7.0.0", 24 | "vite": "^5.1.6", 25 | "vue-template-compiler": "^2.7.16" 26 | }, 27 | "eslintConfig": { 28 | "root": true, 29 | "env": { 30 | "node": true 31 | }, 32 | "extends": [ 33 | "plugin:vue/vue3-essential", 34 | "eslint:recommended" 35 | ], 36 | "parserOptions": { 37 | "parser": "babel-eslint" 38 | }, 39 | "rules": {} 40 | }, 41 | "browserslist": [ 42 | "> 1%", 43 | "last 2 versions", 44 | "not dead" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DVBProject/DVB-I-Reference-CSR/688e3fc116b0dfcc44a31630369c329e053dfc16/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 102 | 103 | 137 | -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DVBProject/DVB-I-Reference-CSR/688e3fc116b0dfcc44a31630369c329e053dfc16/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/src/assets/logo_dvb-i.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DVBProject/DVB-I-Reference-CSR/688e3fc116b0dfcc44a31630369c329e053dfc16/frontend/src/assets/logo_dvb-i.png -------------------------------------------------------------------------------- /frontend/src/assets/logo_sofiadigital.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DVBProject/DVB-I-Reference-CSR/688e3fc116b0dfcc44a31630369c329e053dfc16/frontend/src/assets/logo_sofiadigital.png -------------------------------------------------------------------------------- /frontend/src/components/Login/Login.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 60 | 61 | 68 | -------------------------------------------------------------------------------- /frontend/src/components/Login/Logout.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 44 | 45 | 52 | -------------------------------------------------------------------------------- /frontend/src/components/Login/Setup.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/User/Profile.vue: -------------------------------------------------------------------------------- 1 | 99 | 100 | 297 | 298 | 325 | -------------------------------------------------------------------------------- /frontend/src/components/admin/AddUser.vue: -------------------------------------------------------------------------------- 1 | 92 | 93 | 236 | 237 | 248 | -------------------------------------------------------------------------------- /frontend/src/components/admin/AdminView.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 94 | 95 | 106 | -------------------------------------------------------------------------------- /frontend/src/components/providers/ProviderList.vue: -------------------------------------------------------------------------------- 1 | 120 | 121 | 277 | 278 | 289 | -------------------------------------------------------------------------------- /frontend/src/components/providers/ProviderView.vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 266 | 267 | 299 | -------------------------------------------------------------------------------- /frontend/src/components/servicelist/ServiceListList.vue: -------------------------------------------------------------------------------- 1 | 144 | 145 | 254 | 255 | 266 | -------------------------------------------------------------------------------- /frontend/src/countries.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* eslint-env es6 */ 3 | // https://raw.githubusercontent.com/lukes/ISO-3166-Countries-with-Regional-Codes/master/slim-3/slim-3.json 4 | 5 | const countries = { 6 | AFG: { name: "Afghanistan" }, 7 | ALA: { name: "Åland Islands" }, 8 | ALB: { name: "Albania" }, 9 | DZA: { name: "Algeria" }, 10 | ASM: { name: "American Samoa" }, 11 | AND: { name: "Andorra" }, 12 | AGO: { name: "Angola" }, 13 | AIA: { name: "Anguilla" }, 14 | ATA: { name: "Antarctica" }, 15 | ATG: { name: "Antigua and Barbuda" }, 16 | ARG: { name: "Argentina" }, 17 | ARM: { name: "Armenia" }, 18 | ABW: { name: "Aruba" }, 19 | AUS: { name: "Australia" }, 20 | AUT: { name: "Austria" }, 21 | AZE: { name: "Azerbaijan" }, 22 | BHS: { name: "Bahamas" }, 23 | BHR: { name: "Bahrain" }, 24 | BGD: { name: "Bangladesh" }, 25 | BRB: { name: "Barbados" }, 26 | BLR: { name: "Belarus" }, 27 | BEL: { name: "Belgium" }, 28 | BLZ: { name: "Belize" }, 29 | BEN: { name: "Benin" }, 30 | BMU: { name: "Bermuda" }, 31 | BTN: { name: "Bhutan" }, 32 | BOL: { name: "Bolivia (Plurinational State of)" }, 33 | BES: { name: "Bonaire, Sint Eustatius and Saba" }, 34 | BIH: { name: "Bosnia and Herzegovina" }, 35 | BWA: { name: "Botswana" }, 36 | BVT: { name: "Bouvet Island" }, 37 | BRA: { name: "Brazil" }, 38 | IOT: { name: "British Indian Ocean Territory" }, 39 | BRN: { name: "Brunei Darussalam" }, 40 | BGR: { name: "Bulgaria" }, 41 | BFA: { name: "Burkina Faso" }, 42 | BDI: { name: "Burundi" }, 43 | CPV: { name: "Cabo Verde" }, 44 | KHM: { name: "Cambodia" }, 45 | CMR: { name: "Cameroon" }, 46 | CAN: { name: "Canada" }, 47 | CYM: { name: "Cayman Islands" }, 48 | CAF: { name: "Central African Republic" }, 49 | TCD: { name: "Chad" }, 50 | CHL: { name: "Chile" }, 51 | CHN: { name: "China" }, 52 | CXR: { name: "Christmas Island" }, 53 | CCK: { name: "Cocos (Keeling) Islands" }, 54 | COL: { name: "Colombia" }, 55 | COM: { name: "Comoros" }, 56 | COG: { name: "Congo" }, 57 | COD: { name: "Congo, Democratic Republic of the" }, 58 | COK: { name: "Cook Islands" }, 59 | CRI: { name: "Costa Rica" }, 60 | CIV: { name: "Côte d'Ivoire" }, 61 | HRV: { name: "Croatia" }, 62 | CUB: { name: "Cuba" }, 63 | CUW: { name: "Curaçao" }, 64 | CYP: { name: "Cyprus" }, 65 | CZE: { name: "Czechia" }, 66 | DNK: { name: "Denmark" }, 67 | DJI: { name: "Djibouti" }, 68 | DMA: { name: "Dominica" }, 69 | DOM: { name: "Dominican Republic" }, 70 | ECU: { name: "Ecuador" }, 71 | EGY: { name: "Egypt" }, 72 | SLV: { name: "El Salvador" }, 73 | GNQ: { name: "Equatorial Guinea" }, 74 | ERI: { name: "Eritrea" }, 75 | EST: { name: "Estonia" }, 76 | SWZ: { name: "Eswatini" }, 77 | ETH: { name: "Ethiopia" }, 78 | FLK: { name: "Falkland Islands (Malvinas)" }, 79 | FRO: { name: "Faroe Islands" }, 80 | FJI: { name: "Fiji" }, 81 | FIN: { name: "Finland" }, 82 | FRA: { name: "France" }, 83 | GUF: { name: "French Guiana" }, 84 | PYF: { name: "French Polynesia" }, 85 | ATF: { name: "French Southern Territories" }, 86 | GAB: { name: "Gabon" }, 87 | GMB: { name: "Gambia" }, 88 | GEO: { name: "Georgia" }, 89 | DEU: { name: "Germany" }, 90 | GHA: { name: "Ghana" }, 91 | GIB: { name: "Gibraltar" }, 92 | GRC: { name: "Greece" }, 93 | GRL: { name: "Greenland" }, 94 | GRD: { name: "Grenada" }, 95 | GLP: { name: "Guadeloupe" }, 96 | GUM: { name: "Guam" }, 97 | GTM: { name: "Guatemala" }, 98 | GGY: { name: "Guernsey" }, 99 | GIN: { name: "Guinea" }, 100 | GNB: { name: "Guinea-Bissau" }, 101 | GUY: { name: "Guyana" }, 102 | HTI: { name: "Haiti" }, 103 | HMD: { name: "Heard Island and McDonald Islands" }, 104 | VAT: { name: "Holy See" }, 105 | HND: { name: "Honduras" }, 106 | HKG: { name: "Hong Kong" }, 107 | HUN: { name: "Hungary" }, 108 | ISL: { name: "Iceland" }, 109 | IND: { name: "India" }, 110 | IDN: { name: "Indonesia" }, 111 | IRN: { name: "Iran (Islamic Republic of)" }, 112 | IRQ: { name: "Iraq" }, 113 | IRL: { name: "Ireland" }, 114 | IMN: { name: "Isle of Man" }, 115 | ISR: { name: "Israel" }, 116 | ITA: { name: "Italy" }, 117 | JAM: { name: "Jamaica" }, 118 | JPN: { name: "Japan" }, 119 | JEY: { name: "Jersey" }, 120 | JOR: { name: "Jordan" }, 121 | KAZ: { name: "Kazakhstan" }, 122 | KEN: { name: "Kenya" }, 123 | KIR: { name: "Kiribati" }, 124 | PRK: { name: "Korea (Democratic People's Republic of)" }, 125 | KOR: { name: "Korea, Republic of" }, 126 | KWT: { name: "Kuwait" }, 127 | KGZ: { name: "Kyrgyzstan" }, 128 | LAO: { name: "Lao People's Democratic Republic" }, 129 | LVA: { name: "Latvia" }, 130 | LBN: { name: "Lebanon" }, 131 | LSO: { name: "Lesotho" }, 132 | LBR: { name: "Liberia" }, 133 | LBY: { name: "Libya" }, 134 | LIE: { name: "Liechtenstein" }, 135 | LTU: { name: "Lithuania" }, 136 | LUX: { name: "Luxembourg" }, 137 | MAC: { name: "Macao" }, 138 | MDG: { name: "Madagascar" }, 139 | MWI: { name: "Malawi" }, 140 | MYS: { name: "Malaysia" }, 141 | MDV: { name: "Maldives" }, 142 | MLI: { name: "Mali" }, 143 | MLT: { name: "Malta" }, 144 | MHL: { name: "Marshall Islands" }, 145 | MTQ: { name: "Martinique" }, 146 | MRT: { name: "Mauritania" }, 147 | MUS: { name: "Mauritius" }, 148 | MYT: { name: "Mayotte" }, 149 | MEX: { name: "Mexico" }, 150 | FSM: { name: "Micronesia (Federated States of)" }, 151 | MDA: { name: "Moldova, Republic of" }, 152 | MCO: { name: "Monaco" }, 153 | MNG: { name: "Mongolia" }, 154 | MNE: { name: "Montenegro" }, 155 | MSR: { name: "Montserrat" }, 156 | MAR: { name: "Morocco" }, 157 | MOZ: { name: "Mozambique" }, 158 | MMR: { name: "Myanmar" }, 159 | NAM: { name: "Namibia" }, 160 | NRU: { name: "Nauru" }, 161 | NPL: { name: "Nepal" }, 162 | NLD: { name: "Netherlands" }, 163 | NCL: { name: "New Caledonia" }, 164 | NZL: { name: "New Zealand" }, 165 | NIC: { name: "Nicaragua" }, 166 | NER: { name: "Niger" }, 167 | NGA: { name: "Nigeria" }, 168 | NIU: { name: "Niue" }, 169 | NFK: { name: "Norfolk Island" }, 170 | MKD: { name: "North Macedonia" }, 171 | MNP: { name: "Northern Mariana Islands" }, 172 | NOR: { name: "Norway" }, 173 | OMN: { name: "Oman" }, 174 | PAK: { name: "Pakistan" }, 175 | PLW: { name: "Palau" }, 176 | PSE: { name: "Palestine, State of" }, 177 | PAN: { name: "Panama" }, 178 | PNG: { name: "Papua New Guinea" }, 179 | PRY: { name: "Paraguay" }, 180 | PER: { name: "Peru" }, 181 | PHL: { name: "Philippines" }, 182 | PCN: { name: "Pitcairn" }, 183 | POL: { name: "Poland" }, 184 | PRT: { name: "Portugal" }, 185 | PRI: { name: "Puerto Rico" }, 186 | QAT: { name: "Qatar" }, 187 | REU: { name: "Réunion" }, 188 | ROU: { name: "Romania" }, 189 | RUS: { name: "Russian Federation" }, 190 | RWA: { name: "Rwanda" }, 191 | BLM: { name: "Saint Barthélemy" }, 192 | SHN: { name: "Saint Helena, Ascension and Tristan da Cunha" }, 193 | KNA: { name: "Saint Kitts and Nevis" }, 194 | LCA: { name: "Saint Lucia" }, 195 | MAF: { name: "Saint Martin (French part)" }, 196 | SPM: { name: "Saint Pierre and Miquelon" }, 197 | VCT: { name: "Saint Vincent and the Grenadines" }, 198 | WSM: { name: "Samoa" }, 199 | SMR: { name: "San Marino" }, 200 | STP: { name: "Sao Tome and Principe" }, 201 | SAU: { name: "Saudi Arabia" }, 202 | SEN: { name: "Senegal" }, 203 | SRB: { name: "Serbia" }, 204 | SYC: { name: "Seychelles" }, 205 | SLE: { name: "Sierra Leone" }, 206 | SGP: { name: "Singapore" }, 207 | SXM: { name: "Sint Maarten (Dutch part)" }, 208 | SVK: { name: "Slovakia" }, 209 | SVN: { name: "Slovenia" }, 210 | SLB: { name: "Solomon Islands" }, 211 | SOM: { name: "Somalia" }, 212 | ZAF: { name: "South Africa" }, 213 | SGS: { name: "South Georgia and the South Sandwich Islands" }, 214 | SSD: { name: "South Sudan" }, 215 | ESP: { name: "Spain" }, 216 | LKA: { name: "Sri Lanka" }, 217 | SDN: { name: "Sudan" }, 218 | SUR: { name: "Suriname" }, 219 | SJM: { name: "Svalbard and Jan Mayen" }, 220 | SWE: { name: "Sweden" }, 221 | CHE: { name: "Switzerland" }, 222 | SYR: { name: "Syrian Arab Republic" }, 223 | TWN: { name: "Taiwan, Province of China" }, 224 | TJK: { name: "Tajikistan" }, 225 | TZA: { name: "Tanzania, United Republic of" }, 226 | THA: { name: "Thailand" }, 227 | TLS: { name: "Timor-Leste" }, 228 | TGO: { name: "Togo" }, 229 | TKL: { name: "Tokelau" }, 230 | TON: { name: "Tonga" }, 231 | TTO: { name: "Trinidad and Tobago" }, 232 | TUN: { name: "Tunisia" }, 233 | TUR: { name: "Turkey" }, 234 | TKM: { name: "Turkmenistan" }, 235 | TCA: { name: "Turks and Caicos Islands" }, 236 | TUV: { name: "Tuvalu" }, 237 | UGA: { name: "Uganda" }, 238 | UKR: { name: "Ukraine" }, 239 | ARE: { name: "United Arab Emirates" }, 240 | GBR: { name: "United Kingdom of Great Britain and Northern Ireland" }, 241 | USA: { name: "United States of America" }, 242 | UMI: { name: "United States Minor Outlying Islands" }, 243 | URY: { name: "Uruguay" }, 244 | UZB: { name: "Uzbekistan" }, 245 | VUT: { name: "Vanuatu" }, 246 | VEN: { name: "Venezuela (Bolivarian Republic of)" }, 247 | VNM: { name: "Viet Nam" }, 248 | VGB: { name: "Virgin Islands (British)" }, 249 | VIR: { name: "Virgin Islands (U.S.)" }, 250 | WLF: { name: "Wallis and Futuna" }, 251 | ESH: { name: "Western Sahara" }, 252 | YEM: { name: "Yemen" }, 253 | ZMB: { name: "Zambia" }, 254 | ZWE: { name: "Zimbabwe" }, 255 | }; 256 | 257 | export default countries; 258 | -------------------------------------------------------------------------------- /frontend/src/http-common.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default axios.create({ 4 | baseURL: import.meta.env.VITE_APP_BACKEND_URL, 5 | headers: { 6 | "Content-type": "application/json", 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /frontend/src/languages.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* eslint-env es6 */ 3 | // https://github.com/haliaeetus/iso-639/blob/master/data/iso_639-2.json 4 | 5 | const languages = { 6 | aa: { name: "Afar" }, 7 | ab: { name: "Abkhazian" }, 8 | af: { name: "Afrikaans" }, 9 | ak: { name: "Akan" }, 10 | sq: { name: "Albanian" }, 11 | am: { name: "Amharic" }, 12 | ar: { name: "Arabic" }, 13 | an: { name: "Aragonese" }, 14 | hy: { name: "Armenian" }, 15 | as: { name: "Assamese" }, 16 | av: { name: "Avaric" }, 17 | ae: { name: "Avestan" }, 18 | ay: { name: "Aymara" }, 19 | az: { name: "Azerbaijani" }, 20 | ba: { name: "Bashkir" }, 21 | bm: { name: "Bambara" }, 22 | eu: { name: "Basque" }, 23 | be: { name: "Belarusian" }, 24 | bn: { name: "Bengali" }, 25 | bh: { name: "Bihari languages" }, 26 | bi: { name: "Bislama" }, 27 | bo: { name: "Tibetan" }, 28 | bs: { name: "Bosnian" }, 29 | br: { name: "Breton" }, 30 | bg: { name: "Bulgarian" }, 31 | my: { name: "Burmese" }, 32 | ca: { name: "Catalan" }, 33 | cs: { name: "Czech" }, 34 | ch: { name: "Chamorro" }, 35 | ce: { name: "Chechen" }, 36 | zh: { name: "Chinese" }, 37 | cu: { name: "Church Slavic" }, 38 | cv: { name: "Chuvash" }, 39 | kw: { name: "Cornish" }, 40 | co: { name: "Corsican" }, 41 | cr: { name: "Cree" }, 42 | cy: { name: "Welsh" }, 43 | da: { name: "Danish" }, 44 | de: { name: "German" }, 45 | dv: { name: "Divehi" }, 46 | nl: { name: "Dutch" }, 47 | dz: { name: "Dzongkha" }, 48 | el: { name: "Greek, Modern (1453-)" }, 49 | en: { name: "English" }, 50 | eo: { name: "Esperanto" }, 51 | et: { name: "Estonian" }, 52 | ee: { name: "Ewe" }, 53 | fo: { name: "Faroese" }, 54 | fa: { name: "Persian" }, 55 | fj: { name: "Fijian" }, 56 | fi: { name: "Finnish" }, 57 | fr: { name: "French" }, 58 | fy: { name: "Western Frisian" }, 59 | ff: { name: "Fulah" }, 60 | ka: { name: "Georgian" }, 61 | gd: { name: "Gaelic" }, 62 | ga: { name: "Irish" }, 63 | gl: { name: "Galician" }, 64 | gv: { name: "Manx" }, 65 | gn: { name: "Guarani" }, 66 | gu: { name: "Gujarati" }, 67 | ht: { name: "Haitian" }, 68 | ha: { name: "Hausa" }, 69 | he: { name: "Hebrew" }, 70 | hz: { name: "Herero" }, 71 | hi: { name: "Hindi" }, 72 | ho: { name: "Hiri Motu" }, 73 | hr: { name: "Croatian" }, 74 | hu: { name: "Hungarian" }, 75 | ig: { name: "Igbo" }, 76 | is: { name: "Icelandic" }, 77 | io: { name: "Ido" }, 78 | ii: { name: "Sichuan Yi" }, 79 | iu: { name: "Inuktitut" }, 80 | ie: { name: "Interlingue" }, 81 | ia: { name: "Interlingua (International Auxiliary Language Association)" }, 82 | id: { name: "Indonesian" }, 83 | ik: { name: "Inupiaq" }, 84 | it: { name: "Italian" }, 85 | jv: { name: "Javanese" }, 86 | ja: { name: "Japanese" }, 87 | kl: { name: "Kalaallisut" }, 88 | kn: { name: "Kannada" }, 89 | ks: { name: "Kashmiri" }, 90 | kr: { name: "Kanuri" }, 91 | kk: { name: "Kazakh" }, 92 | km: { name: "Central Khmer" }, 93 | ki: { name: "Kikuyu" }, 94 | rw: { name: "Kinyarwanda" }, 95 | ky: { name: "Kirghiz" }, 96 | kv: { name: "Komi" }, 97 | kg: { name: "Kongo" }, 98 | ko: { name: "Korean" }, 99 | kj: { name: "Kuanyama" }, 100 | ku: { name: "Kurdish" }, 101 | lo: { name: "Lao" }, 102 | la: { name: "Latin" }, 103 | lv: { name: "Latvian" }, 104 | li: { name: "Limburgan" }, 105 | ln: { name: "Lingala" }, 106 | lt: { name: "Lithuanian" }, 107 | lb: { name: "Luxembourgish" }, 108 | lu: { name: "Luba-Katanga" }, 109 | lg: { name: "Ganda" }, 110 | mk: { name: "Macedonian" }, 111 | mh: { name: "Marshallese" }, 112 | ml: { name: "Malayalam" }, 113 | mi: { name: "Maori" }, 114 | mr: { name: "Marathi" }, 115 | ms: { name: "Malay" }, 116 | mg: { name: "Malagasy" }, 117 | mt: { name: "Maltese" }, 118 | mn: { name: "Mongolian" }, 119 | na: { name: "Nauru" }, 120 | nv: { name: "Navajo" }, 121 | nr: { name: "Ndebele, South" }, 122 | nd: { name: "Ndebele, North" }, 123 | ng: { name: "Ndonga" }, 124 | ne: { name: "Nepali" }, 125 | nn: { name: "Norwegian Nynorsk" }, 126 | nb: { name: "Bokmål, Norwegian" }, 127 | no: { name: "Norwegian" }, 128 | ny: { name: "Chichewa" }, 129 | oc: { name: "Occitan (post 1500)" }, 130 | oj: { name: "Ojibwa" }, 131 | or: { name: "Oriya" }, 132 | om: { name: "Oromo" }, 133 | os: { name: "Ossetian" }, 134 | pa: { name: "Panjabi" }, 135 | pi: { name: "Pali" }, 136 | pl: { name: "Polish" }, 137 | pt: { name: "Portuguese" }, 138 | ps: { name: "Pushto" }, 139 | qu: { name: "Quechua" }, 140 | rm: { name: "Romansh" }, 141 | ro: { name: "Romanian" }, 142 | rn: { name: "Rundi" }, 143 | ru: { name: "Russian" }, 144 | sg: { name: "Sango" }, 145 | sa: { name: "Sanskrit" }, 146 | si: { name: "Sinhala" }, 147 | sk: { name: "Slovak" }, 148 | sl: { name: "Slovenian" }, 149 | se: { name: "Northern Sami" }, 150 | sm: { name: "Samoan" }, 151 | sn: { name: "Shona" }, 152 | sd: { name: "Sindhi" }, 153 | so: { name: "Somali" }, 154 | st: { name: "Sotho, Southern" }, 155 | es: { name: "Spanish" }, 156 | sc: { name: "Sardinian" }, 157 | sr: { name: "Serbian" }, 158 | ss: { name: "Swati" }, 159 | su: { name: "Sundanese" }, 160 | sw: { name: "Swahili" }, 161 | sv: { name: "Swedish" }, 162 | ty: { name: "Tahitian" }, 163 | ta: { name: "Tamil" }, 164 | tt: { name: "Tatar" }, 165 | te: { name: "Telugu" }, 166 | tg: { name: "Tajik" }, 167 | tl: { name: "Tagalog" }, 168 | th: { name: "Thai" }, 169 | ti: { name: "Tigrinya" }, 170 | to: { name: "Tonga (Tonga Islands)" }, 171 | tn: { name: "Tswana" }, 172 | ts: { name: "Tsonga" }, 173 | tk: { name: "Turkmen" }, 174 | tr: { name: "Turkish" }, 175 | tw: { name: "Twi" }, 176 | ug: { name: "Uighur" }, 177 | uk: { name: "Ukrainian" }, 178 | ur: { name: "Urdu" }, 179 | uz: { name: "Uzbek" }, 180 | ve: { name: "Venda" }, 181 | vi: { name: "Vietnamese" }, 182 | vo: { name: "Volapük" }, 183 | wa: { name: "Walloon" }, 184 | wo: { name: "Wolof" }, 185 | xh: { name: "Xhosa" }, 186 | yi: { name: "Yiddish" }, 187 | yo: { name: "Yoruba" }, 188 | za: { name: "Zhuang" }, 189 | zu: { name: "Zulu" }, 190 | }; 191 | 192 | export default languages; 193 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | import "bootstrap"; 4 | import "bootstrap/dist/css/bootstrap.min.css"; 5 | import router from "./router"; 6 | 7 | createApp(App) 8 | .use(router) 9 | // .component('multiselect', Multiselect) 10 | .mount("#app"); 11 | -------------------------------------------------------------------------------- /frontend/src/router.js: -------------------------------------------------------------------------------- 1 | import { createWebHistory, createRouter } from "vue-router"; 2 | 3 | import LoginService from "./services/LoginService"; 4 | 5 | import Login from "./components/Login/Login.vue"; 6 | import Logout from "./components/Login/Logout.vue"; 7 | 8 | const routes = [ 9 | { 10 | path: "/providers", 11 | alias: "/providers", 12 | name: "providers", 13 | component: () => import("./components/providers/ProviderList.vue"), 14 | meta: { 15 | requiresAuth: true, 16 | }, 17 | }, 18 | { 19 | path: "/providers/:id", 20 | name: "provider-details", 21 | component: () => import("./components/providers/Provider.vue"), 22 | meta: { 23 | requiresAuth: true, 24 | }, 25 | }, 26 | { 27 | path: "/add-provider", 28 | name: "add-provider", 29 | component: () => import("./components/providers/Provider.vue"), 30 | meta: { 31 | requiresAuth: true, 32 | }, 33 | }, 34 | { 35 | path: "/", 36 | alias: "/servicelists", 37 | name: "servicelists", 38 | component: () => import("./components/servicelist/ServiceListList.vue"), 39 | meta: { 40 | requiresAuth: true, 41 | //is_admin: true 42 | }, 43 | }, 44 | { 45 | path: "/servicelists/:id", 46 | name: "servicelist-details", 47 | component: () => import("./components/servicelist/ServiceList.vue"), 48 | meta: { 49 | requiresAuth: true, 50 | //is_admin: true 51 | }, 52 | }, 53 | { 54 | path: "/add-servicelist", 55 | name: "add-servicelist", 56 | component: () => import("./components/servicelist/ServiceList.vue"), 57 | meta: { 58 | requiresAuth: true, 59 | //is_admin: true 60 | }, 61 | }, 62 | { 63 | path: "/login", 64 | alias: "/authenticate", 65 | name: "login", 66 | component: Login, 67 | }, 68 | { 69 | path: "/logout", 70 | name: "logout", 71 | component: Logout, 72 | }, 73 | 74 | { 75 | path: "/setup", 76 | alias: "/setup", 77 | name: "setup", 78 | component: () => import("./components/Login/Setup.vue"), 79 | meta: { 80 | guest: true, 81 | }, 82 | }, 83 | { 84 | path: "/settings", 85 | alias: "/settings", 86 | name: "settings", 87 | meta: { 88 | requiresAuth: true, 89 | is_admin: true, 90 | }, 91 | component: () => import("./components/settings/Settings.vue"), 92 | }, 93 | { 94 | path: "/admin", 95 | name: "admin", 96 | component: () => import("./components/admin/AdminView.vue"), 97 | meta: { 98 | requiresAuth: true, 99 | is_admin: true, 100 | }, 101 | }, 102 | { 103 | path: "/admin/user/:id", 104 | name: "user-edit", 105 | component: () => import("./components/admin/EditUser.vue"), 106 | meta: { 107 | requiresAuth: true, 108 | is_admin: true, 109 | }, 110 | }, 111 | { 112 | path: "/admin/add-user/", 113 | name: "user-add", 114 | component: () => import("./components/admin/AddUser.vue"), 115 | meta: { 116 | requiresAuth: true, 117 | is_admin: true, 118 | }, 119 | }, 120 | { 121 | path: "/view-provider/:id", 122 | name: "provider-view", 123 | component: () => import("./components/providers/ProviderView.vue"), 124 | meta: { 125 | requiresAuth: true, 126 | }, 127 | }, 128 | { 129 | path: "/profile", 130 | name: "profile", 131 | component: () => import("./components/User/Profile.vue"), 132 | meta: { 133 | requiresAuth: true, 134 | }, 135 | }, 136 | { 137 | // redirect for nonexistent routes 138 | path: "/:pathMatch(.*)*", 139 | name: "notfound", 140 | component: () => import("./components/servicelist/ServiceListList.vue"), 141 | }, 142 | ]; 143 | 144 | const router = createRouter({ 145 | history: createWebHistory(), 146 | routes, 147 | }); 148 | // auth & admin checking middleware 149 | router.beforeEach(LoginService.authCheck); 150 | 151 | export default router; 152 | -------------------------------------------------------------------------------- /frontend/src/services/ListProviderDataService.js: -------------------------------------------------------------------------------- 1 | import http from "../http-common"; 2 | 3 | let configdata = { 4 | headers: { Authorization: sessionStorage.getItem("auth") }, 5 | }; 6 | 7 | class ListproviderDataService { 8 | get() { 9 | return http.get(`/listprovider`, configdata); 10 | } 11 | 12 | update(data) { 13 | return http.put(`/listprovider`, data, configdata); 14 | } 15 | } 16 | 17 | export default new ListproviderDataService(); 18 | -------------------------------------------------------------------------------- /frontend/src/services/LoginService.js: -------------------------------------------------------------------------------- 1 | import http from "../http-common"; 2 | 3 | let configdata = { 4 | headers: { Authorization: sessionStorage.getItem("auth") }, 5 | }; 6 | 7 | class LoginService { 8 | async login(data) { 9 | const res = await http.post("/authenticate", data).catch((err) => { 10 | console.log(err); 11 | // clear session storage 12 | sessionStorage.clear(); 13 | }); 14 | 15 | if (res && res.data.success) { 16 | // set sessionStorage items 17 | sessionStorage.setItem("auth", res.data.token); 18 | const user = res.data.user || {}; 19 | sessionStorage.setItem("user", JSON.stringify(user)); 20 | return { success: true }; 21 | } else { 22 | return { success: false }; 23 | } 24 | } 25 | 26 | async logout() { 27 | await http.get("/logout", configdata).catch((err) => { 28 | console.log(err); 29 | }); 30 | // clear session storage 31 | sessionStorage.clear(); 32 | } 33 | 34 | // TODO: handler for request auth exceptions (currently views will redirect to /) 35 | // 36 | 37 | // initial user setup req 38 | setup() { 39 | return http.get("/setup"); 40 | } 41 | 42 | reset() { 43 | console.log("reset"); 44 | // clear session information 45 | sessionStorage.clear(); 46 | // reload previous view & trigger re-login 47 | window.location = "/"; 48 | } 49 | 50 | // ui-routing middleware 51 | authCheck(to, from, next) { 52 | if (to.matched.some((record) => record.meta.requiresAuth)) { 53 | if (sessionStorage.getItem("auth") == null) { 54 | next({ 55 | path: "/login", 56 | params: { nextUrl: to.fullPath }, 57 | }); 58 | } else { 59 | let user = JSON.parse(sessionStorage.getItem("user")); 60 | if (to.matched.some((record) => record.meta.is_admin)) { 61 | if (user.role) { 62 | next(); 63 | } else { 64 | console.log("admin user requested"); 65 | next({ 66 | path: "/login", 67 | params: { nextUrl: to.fullPath }, 68 | }); 69 | } 70 | } else { 71 | next(); 72 | } 73 | } 74 | } else if (to.matched.some((record) => record.meta.guest)) { 75 | if (sessionStorage.getItem("auth") == null) { 76 | next(); 77 | } else { 78 | next({ path: from.fullPath }); 79 | } 80 | } else { 81 | next(); 82 | } 83 | } 84 | } 85 | 86 | export default new LoginService(); 87 | -------------------------------------------------------------------------------- /frontend/src/services/ProviderDataService.js: -------------------------------------------------------------------------------- 1 | import http from "../http-common"; 2 | 3 | let configdata = { 4 | headers: { Authorization: sessionStorage.getItem("auth") }, 5 | }; 6 | 7 | class ProviderDataService { 8 | getAll() { 9 | return http.get("/providers", configdata); 10 | } 11 | 12 | get(id) { 13 | return http.get(`/providers/${id}`, configdata); 14 | } 15 | 16 | create(data) { 17 | return http.post("/providers", data, configdata); 18 | } 19 | 20 | update(id, data) { 21 | return http.put(`/providers/${id}`, data, configdata); 22 | } 23 | 24 | delete(id) { 25 | return http.delete(`/providers/${id}`, configdata); 26 | } 27 | 28 | findByTitle(title) { 29 | return http.get(`/providers?title=${title}`, configdata); 30 | } 31 | } 32 | 33 | export default new ProviderDataService(); 34 | -------------------------------------------------------------------------------- /frontend/src/services/ServiceListDataService.js: -------------------------------------------------------------------------------- 1 | import http from "../http-common"; 2 | 3 | let configdata = { 4 | headers: { Authorization: sessionStorage.getItem("auth") }, 5 | }; 6 | 7 | class ServiceListDataService { 8 | getAll() { 9 | return http.get("/servicelist", configdata); 10 | } 11 | 12 | get(id) { 13 | return http.get(`/servicelist/${id}`, configdata); 14 | } 15 | 16 | create(data) { 17 | return http.post("/servicelist", data, configdata); 18 | } 19 | 20 | update(id, data) { 21 | return http.put(`/servicelist/${id}`, data, configdata); 22 | } 23 | 24 | delete(id) { 25 | return http.delete(`/servicelist/${id}`, configdata); 26 | } 27 | 28 | findByTitle(title) { 29 | return http.get(`/servicelist?title=${title}`, configdata); 30 | } 31 | 32 | getListHistory(id) { 33 | return http.get(`/eventhistory/${id}`, configdata); 34 | } 35 | 36 | getByProvider(id) { 37 | return http.get(`/servicelist/provider/${id}`, configdata); 38 | } 39 | } 40 | 41 | export default new ServiceListDataService(); 42 | -------------------------------------------------------------------------------- /frontend/src/services/UserDataService.js: -------------------------------------------------------------------------------- 1 | import http from "../http-common"; 2 | 3 | let configdata = { 4 | headers: { Authorization: sessionStorage.getItem("auth") }, 5 | }; 6 | 7 | class UserDataService { 8 | getAll() { 9 | return http.get("/users", configdata); 10 | } 11 | 12 | get(id) { 13 | return http.get(`/users/${id}`, configdata); 14 | } 15 | 16 | create(data) { 17 | return http.post("/users", data, configdata); 18 | } 19 | 20 | update(id, data) { 21 | return http.put(`/users/${id}`, data, configdata); 22 | } 23 | 24 | delete(id) { 25 | return http.delete(`/users/${id}`, configdata); 26 | } 27 | 28 | getByProvider(id) { 29 | return http.get(`/users/provider/${id}`, configdata); 30 | } 31 | 32 | changePwd(data) { 33 | return http.post("/pwd", data, configdata); 34 | } 35 | } 36 | 37 | export default new UserDataService(); 38 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | import vue from "@vitejs/plugin-vue"; 3 | import { defineConfig } from "vite"; 4 | import path from "path"; 5 | 6 | export default defineConfig({ 7 | plugins: [vue()], 8 | resolve: { 9 | alias: { 10 | "@": path.resolve(__dirname, "./src"), 11 | }, 12 | }, 13 | define: { 14 | __BUILD_DATE__: new Date(), 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /utils/genreparser/genreparser.js: -------------------------------------------------------------------------------- 1 | const { DOMParser } = require("@xmldom/xmldom"); 2 | const fs = require("fs"); 3 | const genres = [ 4 | "https://raw.githubusercontent.com/paulhiggs/dvb-i-tools/main/tva/ContentCS.xml", 5 | "https://raw.githubusercontent.com/paulhiggs/dvb-i-tools/main/tva/FormatCS.xml", 6 | "https://raw.githubusercontent.com/paulhiggs/dvb-i-tools/main/dvbi/DVBContentSubjectCS-2019.xml", 7 | ]; 8 | 9 | const getXML = (url) => { 10 | return new Promise((resolve, reject) => { 11 | const https = require("https"); 12 | https 13 | .get(url, (resp) => { 14 | let data = ""; 15 | 16 | // A chunk of data has been recieved. 17 | resp.on("data", (chunk) => { 18 | data += chunk; 19 | }); 20 | 21 | // The whole response has been received. Print out the result. 22 | resp.on("end", () => { 23 | resolve(data); 24 | }); 25 | }) 26 | .on("error", (err) => { 27 | reject(err); 28 | }); 29 | }); 30 | }; 31 | 32 | async function readGenres() { 33 | let genrelist = {}; 34 | for (const genre of genres) { 35 | let data = await getXML(genre); 36 | const parser = new DOMParser(); 37 | const doc = parser.parseFromString(data, "text/xml"); 38 | const uri = doc.documentElement.getAttribute("uri"); 39 | const terms = doc.documentElement.getElementsByTagName("Term"); 40 | for (var i = 0; i < terms.length; i++) { 41 | let term = terms[i]; 42 | let id = term.getAttribute("termID"); 43 | let name = null; 44 | for (var j = 0; j < term.childNodes.length; j++) { 45 | if (term.childNodes[j].nodeType == 1 && term.childNodes[j].tagName == "Name") { 46 | name = term.childNodes[j].childNodes[0].nodeValue; 47 | } 48 | } 49 | genrelist[uri + "." + id] = name; 50 | } 51 | } 52 | writeFile("genres.json", JSON.stringify(genrelist)); 53 | console.log(genrelist); 54 | } 55 | 56 | function writeFile(filename, data) { 57 | fs.writeFile(filename, data, (err) => { 58 | if (err) { 59 | console.log(err, filename); 60 | } else { 61 | console.log("file saved: ", filename); 62 | } 63 | }); 64 | } 65 | readGenres(); 66 | -------------------------------------------------------------------------------- /utils/genreparser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "utils", 3 | "version": "1.0.0", 4 | "main": "genreparser.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "@xmldom/xmldom": "^0.7.5" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /utils/genreparser/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@xmldom/xmldom@^0.7.5": 6 | version "0.7.5" 7 | resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d" 8 | integrity sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A== 9 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | --------------------------------------------------------------------------------