From c6e992a3e2580f8adef183d2bd45c4028a29b088 Mon Sep 17 00:00:00 2001 From: Rripped <75929322+Rripped@users.noreply.github.com> Date: Thu, 17 Mar 2022 17:36:14 +0100 Subject: [PATCH 001/263] Create README.md --- telegram_bot/README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 telegram_bot/README.md diff --git a/telegram_bot/README.md b/telegram_bot/README.md new file mode 100644 index 0000000..abda9e2 --- /dev/null +++ b/telegram_bot/README.md @@ -0,0 +1,6 @@ +## Local setup for telegram bot +0. optional: build virtual env by ``python -m venv venv`` + ``env/Scripts/activate`` +2. create .env and set API keys etc. (use .env.example as a layout) +3. install required libs via ``pip install -r ./telegram_bot/requirements.txt`` +4. run bot.py via ``python ./telegram_bot/bot.py`` From dc7415738e988c48f2ef9acbb9184869fab051f1 Mon Sep 17 00:00:00 2001 From: Rripped <75929322+Rripped@users.noreply.github.com> Date: Thu, 17 Mar 2022 17:36:33 +0100 Subject: [PATCH 002/263] Update README.md --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index 96a70d7..1dbcb5d 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,3 @@ WebEngineering2 Projekt: Aktien und News Bot für Telegram ## Dokumentation - Postman-API -> docs/postman.json - Datenbank -> database/* - -## Local setup for telegram bot -0. optional: build virtual env by ``python -m venv venv`` - ``env/Scripts/activate`` -2. create .env and set API keys etc. (use .env.example as a layout) -3. install required libs via ``pip install -r ./telegram_bot/requirements.txt`` -4. run bot.py via ``python ./telegram_bot/bot.py`` From 23e46635b31a92d123d7b99b5987ea7d12af600e Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Thu, 17 Mar 2022 17:36:54 +0100 Subject: [PATCH 003/263] Updated docs path #2 --- api/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/config.py b/api/config.py index e7f323c..3e68c44 100644 --- a/api/config.py +++ b/api/config.py @@ -24,6 +24,7 @@ class ConfigClass(object): # openapi/Swagger config SPEC_FORMAT = 'yaml' + LOCAL_SPEC_PATH = 'docs/openapi.json' SERVERS = [ { "name": "Production", From c13a980474049c9fa91d3dd18d3c0f8d6fc100ac Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Thu, 17 Mar 2022 17:51:40 +0100 Subject: [PATCH 004/263] Updated docs --- api/config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/config.py b/api/config.py index 3e68c44..0bff4a9 100644 --- a/api/config.py +++ b/api/config.py @@ -23,7 +23,6 @@ class ConfigClass(object): SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids SQLAlchemy warning # openapi/Swagger config - SPEC_FORMAT = 'yaml' LOCAL_SPEC_PATH = 'docs/openapi.json' SERVERS = [ { From ef25a702dd88985f75c1c6aa9da6eba46d90e927 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Thu, 17 Mar 2022 17:54:32 +0100 Subject: [PATCH 005/263] Try to fix cors --- api/app.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/api/app.py b/api/app.py index 88049f6..1c6d2e2 100644 --- a/api/app.py +++ b/api/app.py @@ -18,7 +18,11 @@ def create_app(): application = APIFlask(__name__, docs_path='/api/docs') application.config.from_object("config.ConfigClass") - CORS(application) + CORS(application, resource={ + r"/*": { + "origins": "*" + } + }) application.app_context().push() From 54780c835953e99bc9efc83a00f9dfa1196e5086 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Thu, 17 Mar 2022 18:43:06 +0100 Subject: [PATCH 006/263] Fixed login url --- frontend/src/app/Services/auth.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/Services/auth.service.ts b/frontend/src/app/Services/auth.service.ts index db051dc..ad8a3ee 100644 --- a/frontend/src/app/Services/auth.service.ts +++ b/frontend/src/app/Services/auth.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; -const AUTH_API = 'https://aktienbot.flokaiser.com/api/user'; +const AUTH_API = 'https://aktienbot.flokaiser.com/api/user/'; const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }), }; From 0f32c85c7cf9f2aef9dff75dbdf48f21586c0f1c Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Thu, 17 Mar 2022 19:02:00 +0100 Subject: [PATCH 007/263] Try to fix docs --- api/app.py | 2 +- api/config.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/api/app.py b/api/app.py index 1c6d2e2..1fd2c9f 100644 --- a/api/app.py +++ b/api/app.py @@ -15,7 +15,7 @@ def create_app(): load_dotenv() # Create Flask app load app.config - application = APIFlask(__name__, docs_path='/api/docs') + application = APIFlask(__name__, openapi_blueprint_url_prefix='api/') application.config.from_object("config.ConfigClass") CORS(application, resource={ diff --git a/api/config.py b/api/config.py index 0bff4a9..923f3b8 100644 --- a/api/config.py +++ b/api/config.py @@ -23,7 +23,6 @@ class ConfigClass(object): SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids SQLAlchemy warning # openapi/Swagger config - LOCAL_SPEC_PATH = 'docs/openapi.json' SERVERS = [ { "name": "Production", From b0e3650757b9fc0494011bc43a86d2a7b27a0661 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Thu, 17 Mar 2022 19:09:14 +0100 Subject: [PATCH 008/263] Try to fix docs --- api/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/app.py b/api/app.py index 1fd2c9f..9a26db5 100644 --- a/api/app.py +++ b/api/app.py @@ -15,7 +15,7 @@ def create_app(): load_dotenv() # Create Flask app load app.config - application = APIFlask(__name__, openapi_blueprint_url_prefix='api/') + application = APIFlask(__name__, openapi_blueprint_url_prefix='/api') application.config.from_object("config.ConfigClass") CORS(application, resource={ From 3bf24f493842443ef89159fb90e99384cfd3005b Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Thu, 17 Mar 2022 19:33:38 +0100 Subject: [PATCH 009/263] Fix angular 404 --- frontend/Dockerfile | 1 + frontend/deploy/nginx.conf | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 frontend/deploy/nginx.conf diff --git a/frontend/Dockerfile b/frontend/Dockerfile index f298e1e..15ab872 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -13,6 +13,7 @@ RUN ls /usr/local/app/dist FROM nginx:latest COPY --from=build /usr/local/app/dist/aktienbot /usr/share/nginx/html +COPY frontend/deploy/nginx.conf /etc/nginx HEALTHCHECK --interval=15s --timeout=2s CMD ["./deploy/healthcheck.sh"] diff --git a/frontend/deploy/nginx.conf b/frontend/deploy/nginx.conf new file mode 100644 index 0000000..d8a200f --- /dev/null +++ b/frontend/deploy/nginx.conf @@ -0,0 +1,35 @@ +events { + worker_connections 1024; ## Default: 1024 +} + +http { + ## use mime types + include /etc/nginx/mime.types; + server { + listen 80; + + location / { + root /usr/share/nginx/html; + index index.html; + ## without this our .css are not loaded + try_files $uri $uri/ /index.html?$query_string; + } + } + + ## enable gzip compression + gzip on; + gzip_vary on; + gzip_min_length 256; + gzip_proxied any; + + gzip_types + ## text/html is always compressed : https://nginx.org/en/docs/http/ngx_http_gzip_module.html + text/plain + text/css + text/javascript + application/javascript + application/x-javascript + application/xml + application/json + application/ld+json; +} From 9ea9c2175f38684ba25d2aaec6bf49ed642b41c5 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Thu, 17 Mar 2022 19:44:47 +0100 Subject: [PATCH 010/263] Fixed healthcheck --- frontend/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 15ab872..7b8dde9 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -14,6 +14,7 @@ FROM nginx:latest COPY --from=build /usr/local/app/dist/aktienbot /usr/share/nginx/html COPY frontend/deploy/nginx.conf /etc/nginx +COPY frontend/deploy deploy/ HEALTHCHECK --interval=15s --timeout=2s CMD ["./deploy/healthcheck.sh"] From 97d166d0b14e57684a56a5e8354dbf8c962cabeb Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Thu, 17 Mar 2022 19:57:36 +0100 Subject: [PATCH 011/263] Fixed healthcheck --- frontend/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 7b8dde9..637f6aa 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -13,9 +13,12 @@ RUN ls /usr/local/app/dist FROM nginx:latest COPY --from=build /usr/local/app/dist/aktienbot /usr/share/nginx/html + COPY frontend/deploy/nginx.conf /etc/nginx COPY frontend/deploy deploy/ +RUN chmod +x ./deploy/healthcheck.sh + HEALTHCHECK --interval=15s --timeout=2s CMD ["./deploy/healthcheck.sh"] EXPOSE 80 From 1f802703bd82402934e5d200d2face5d60292f82 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Thu, 17 Mar 2022 22:58:17 +0100 Subject: [PATCH 012/263] Added openapi documentation, updated postman docs --- documentation/api/openapi.json | 1 + documentation/api/postman.json | 2483 ++++++++++++++++++++++++++++---- 2 files changed, 2198 insertions(+), 286 deletions(-) create mode 100644 documentation/api/openapi.json diff --git a/documentation/api/openapi.json b/documentation/api/openapi.json new file mode 100644 index 0000000..7c9d313 --- /dev/null +++ b/documentation/api/openapi.json @@ -0,0 +1 @@ +{"components":{"schemas":{"AdminData":{"properties":{"admin":{"type":"boolean"},"username":{"type":"string"}},"type":"object"},"DeleteSuccessful":{"properties":{},"type":"object"},"DeleteUser":{"properties":{"username":{"type":"string"}},"type":"object"},"HTTPError":{"properties":{"detail":{"type":"object"},"message":{"type":"string"}},"type":"object"},"Keyword":{"properties":{"keyword":{"type":"string"}},"type":"object"},"KeywordResponse":{"properties":{"keyword":{"type":"string"},"s_id":{"type":"integer"},"user_id":{"type":"integer"}},"type":"object"},"LoginData":{"properties":{"password":{"type":"string"},"username":{"type":"string"}},"type":"object"},"Symbol":{"properties":{"symbol":{"type":"string"}},"type":"object"},"SymbolResponse":{"properties":{"s_id":{"type":"integer"},"symbol":{"type":"string"},"user_id":{"type":"integer"}},"type":"object"},"Token":{"properties":{"token":{"type":"string"}},"type":"object"},"Transaction":{"properties":{"count":{"type":"integer"},"price":{"type":"number"},"symbol":{"type":"string"},"time":{"type":"string"},"user_id":{"type":"integer"}},"type":"object"},"Users":{"properties":{"admin":{"type":"boolean"},"password":{"type":"string"},"telegram_name":{"type":"string"},"user_id":{"type":"integer"},"username":{"type":"string"}},"type":"object"},"ValidationError":{"properties":{"detail":{"properties":{"":{"properties":{"":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"type":"object"},"message":{"type":"string"}},"type":"object"}},"securitySchemes":{"BearerAuth":{"scheme":"Bearer","type":"http"}}},"info":{"description":"Webengineering 2 | Telegram Aktienbot","title":"APIFlask","version":"0.0.1"},"openapi":"3.0.3","paths":{"/api/keyword":{"delete":{"description":"Removes existing keyword for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Keyword"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/DeleteSuccessful"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Removes existing keyword","tags":["Keyword"]},"post":{"description":"Adds new keyword for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Keyword"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/KeywordResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Add new keyword","tags":["Keyword"]}},"/api/keywords":{"get":{"description":"Returns all keywords for current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/KeywordResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns all keywords","tags":["Keyword"]}},"/api/portfolio":{"get":{"description":"Returns all shares of current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/200"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns portfolio","tags":["Portfolio"]}},"/api/share":{"delete":{"description":"Removes existing symbol for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Symbol"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/DeleteSuccessful"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Removes existing symbol","tags":["Share"]},"post":{"description":"Adds new symbol for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Symbol"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/SymbolResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Add new symbol","tags":["Share"]}},"/api/shares":{"get":{"description":"Returns all symbols for current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/SymbolResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns all symbols","tags":["Share"]}},"/api/transaction":{"post":{"description":"Adds new transaction for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transaction"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/()"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Adds new transaction","tags":["Transaction"]}},"/api/transactions":{"get":{"description":"Returns all transactions for current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Transaction"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns all transactions","tags":["Transaction"]}},"/api/user":{"delete":{"description":"Deletes user by username","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteUser"}}}},"responses":{"204":{"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Delete user","tags":["Users"]},"get":{"description":"Returns current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Users"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Get current user","tags":["Users"]},"put":{"description":"Changes password and/or username of current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginData"}}}},"responses":{"204":{"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Update user","tags":["Users"]}},"/api/user/login":{"post":{"description":"Returns jwt token if username and password match, otherwise returns error","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginData"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Token"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"}},"summary":"Login","tags":["Users"]}},"/api/user/register":{"post":{"description":"Registers user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginData"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Users"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"}},"summary":"Register","tags":["Users"]}},"/api/user/setAdmin":{"put":{"description":"Set admin state of specified user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminData"}}}},"responses":{"204":{"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Set user admin state","tags":["Users"]}},"/api/users":{"get":{"description":"Returns all existing users as array","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Users"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Get all users","tags":["Users"]}}},"servers":[{"name":"Production","url":"https://aktienbot.flokaiser.com"},{"name":"Local","url":"http://127.0.0.1:5000"}],"tags":[{"name":"Keyword"},{"name":"Share"},{"name":"Transaction"},{"name":"Portfolio"},{"name":"Users"}]} diff --git a/documentation/api/postman.json b/documentation/api/postman.json index f8260ec..e6fee89 100644 --- a/documentation/api/postman.json +++ b/documentation/api/postman.json @@ -1,21 +1,218 @@ { "info": { - "_postman_id": "67da7d20-d7df-4ad3-9289-a6e40b6cd2ec", - "name": "AktienBot", + "_postman_id": "b478b800-af91-45a7-8713-b199bcc41866", + "name": "APIFlask", + "description": "Webengineering 2 | Telegram Aktienbot", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ { - "name": "User", + "name": "Keyword", "item": [ { - "name": "/api/register", + "name": "Removes existing keyword", "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/keyword", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "keyword" + ] + }, + "description": "Removes existing keyword for current user" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/keyword", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "keyword" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": {},\n \"status\": 261201,\n \"text\": \"aliqua amet consectetur elit\"\n}" + }, + { + "name": "Validation error", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/keyword", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "keyword" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/keyword", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "keyword" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + } + ] + }, + { + "name": "Add new keyword", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, "method": "POST", - "header": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"Username\",\n \"password\": \"Password\"\n}", + "raw": "{\n \"keyword\": \"elit Lorem\"\n}", "options": { "raw": { "language": "json" @@ -23,104 +220,251 @@ } }, "url": { - "raw": "{{BASE_URL}}/api/register", + "raw": "{{baseUrl}}/api/keyword", "host": [ - "{{BASE_URL}}" + "{{baseUrl}}" ], "path": [ "api", - "register" + "keyword" ] - } - }, - "response": [] - }, - { - "name": "/api/login", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"username\": \"Username\",\n \"password\": \"Password\"\n}", - "options": { - "raw": { - "language": "json" - } - } }, - "url": { - "raw": "{{BASE_URL}}/api/login", - "host": [ - "{{BASE_URL}}" - ], - "path": [ - "api", - "login" - ] - } + "description": "Adds new keyword for current user" }, - "response": [] + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/keyword", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "keyword" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": [\n {\n \"keyword\": \"tempor dolor commodo ipsum non\",\n \"s_id\": -64068772,\n \"user_id\": 82510382\n },\n {\n \"keyword\": \"in aute ex\",\n \"s_id\": 60028598,\n \"user_id\": -36905324\n }\n ],\n \"status\": 63124172,\n \"text\": \"amet\"\n}" + }, + { + "name": "Validation error", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/keyword", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "keyword" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/keyword", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "keyword" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + } + ] }, { - "name": "/api/logout", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, + "name": "Returns all keywords", "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"username\": \"Username\",\n \"password\": \"Password\"\n}", - "options": { - "raw": { - "language": "json" - } + "header": [ + { + "key": "Accept", + "value": "application/json" } - }, + ], "url": { - "raw": "{{BASE_URL}}/api/logout", + "raw": "{{baseUrl}}/api/keywords", "host": [ - "{{BASE_URL}}" + "{{baseUrl}}" ], "path": [ "api", - "logout" + "keywords" ] - } - }, - "response": [] - }, - { - "name": "/api/users", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"username\": \"Username\",\n \"password\": \"Password\"\n}", - "options": { - "raw": { - "language": "json" - } - } }, - "url": { - "raw": "{{BASE_URL}}/api/users", - "host": [ - "{{BASE_URL}}" - ], - "path": [ - "api", - "users" - ] - } + "description": "Returns all keywords for current user" }, - "response": [] + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/keywords", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "keywords" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": [\n {\n \"keyword\": \"tempor dolor commodo ipsum non\",\n \"s_id\": -64068772,\n \"user_id\": 82510382\n },\n {\n \"keyword\": \"in aute ex\",\n \"s_id\": 60028598,\n \"user_id\": -36905324\n }\n ],\n \"status\": 63124172,\n \"text\": \"amet\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/keywords", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "keywords" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + } + ] } ] }, @@ -128,115 +472,494 @@ "name": "Share", "item": [ { - "name": "/api/shares", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, + "name": "Removes existing symbol", "request": { - "method": "GET", - "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", - "raw": "" + "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "options": { + "raw": { + "language": "json" + } + } }, "url": { - "raw": "{{BASE_URL}}/api/shares", + "raw": "{{baseUrl}}/api/share", "host": [ - "{{BASE_URL}}" + "{{baseUrl}}" + ], + "path": [ + "api", + "share" + ] + }, + "description": "Removes existing symbol for current user" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/share", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "share" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": {},\n \"status\": 261201,\n \"text\": \"aliqua amet consectetur elit\"\n}" + }, + { + "name": "Validation error", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/share", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "share" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/share", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "share" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + } + ] + }, + { + "name": "Add new symbol", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/share", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "share" + ] + }, + "description": "Adds new symbol for current user" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/share", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "share" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": [\n {\n \"s_id\": -53227093,\n \"symbol\": \"veniam ea amet irure\",\n \"user_id\": 7468241\n },\n {\n \"s_id\": -37877246,\n \"symbol\": \"ut tempor labore non\",\n \"user_id\": -72644124\n }\n ],\n \"status\": 87653200,\n \"text\": \"aliquip reprehenderit dolore\"\n}" + }, + { + "name": "Validation error", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/share", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "share" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/share", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "share" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + } + ] + }, + { + "name": "Returns all symbols", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/api/shares", + "host": [ + "{{baseUrl}}" ], "path": [ "api", "shares" ] - } - }, - "response": [] - }, - { - "name": "/api/share", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"symbol\": \"DTEGY\"\n}", - "options": { - "raw": { - "language": "json" - } - } }, - "url": { - "raw": "{{BASE_URL}}/api/share", - "host": [ - "{{BASE_URL}}" - ], - "path": [ - "api", - "share" - ] - } + "description": "Returns all symbols for current user" }, - "response": [] - }, - { - "name": "/api/share", - "request": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"symbol\": \"DTEGY\"\n}", - "options": { - "raw": { - "language": "json" + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/shares", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "shares" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" } - } - }, - "url": { - "raw": "{{BASE_URL}}/api/share", - "host": [ - "{{BASE_URL}}" ], - "path": [ - "api", - "share" - ] + "cookie": [], + "body": "{\n \"data\": [\n {\n \"s_id\": -53227093,\n \"symbol\": \"veniam ea amet irure\",\n \"user_id\": 7468241\n },\n {\n \"s_id\": -37877246,\n \"symbol\": \"ut tempor labore non\",\n \"user_id\": -72644124\n }\n ],\n \"status\": 87653200,\n \"text\": \"aliquip reprehenderit dolore\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/shares", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "shares" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" } - }, - "response": [] + ] } ] }, { - "name": "Keyword", + "name": "Transaction", "item": [ { - "name": "/api/keywords", + "name": "Adds new transaction", "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{BASE_URL}}/api/keywords", - "host": [ - "{{BASE_URL}}" - ], - "path": [ - "api", - "keywords" + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } ] - } - }, - "response": [] - }, - { - "name": "/api/keyword", - "request": { + }, "method": "POST", - "header": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], "body": { "mode": "raw", - "raw": "{\n \"keyword\": \"Elon\"\n}", + "raw": "{\n \"count\": 96634001,\n \"price\": -71028444.15053315,\n \"symbol\": \"deserunt officia dolore\",\n \"time\": \"magna occaecat est\",\n \"user_id\": -71272721\n}", "options": { "raw": { "language": "json" @@ -244,94 +967,251 @@ } }, "url": { - "raw": "{{BASE_URL}}/api/keyword", + "raw": "{{baseUrl}}/api/transaction", "host": [ - "{{BASE_URL}}" - ], - "path": [ - "api", - "keyword" - ] - } - }, - "response": [] - }, - { - "name": "/api/keyword", - "request": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"keyword\": \"Elon\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{BASE_URL}}/api/keyword", - "host": [ - "{{BASE_URL}}" - ], - "path": [ - "api", - "keyword" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Transactions", - "item": [ - { - "name": "/api/transactions", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "{{BASE_URL}}/api/transactions", - "host": [ - "{{BASE_URL}}" - ], - "path": [ - "api", - "transactions" - ] - } - }, - "response": [] - }, - { - "name": "/api/transaction", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"symbol\": \"DTEGY\",\n \"time\": \"2021-03-14T18:08:44.625Z\",\n \"count\": 1,\n \"price\": 10.0\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{BASE_URL}}/api/transaction", - "host": [ - "{{BASE_URL}}" + "{{baseUrl}}" ], "path": [ "api", "transaction" ] - } + }, + "description": "Adds new transaction for current user" }, - "response": [] + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"count\": 96634001,\n \"price\": -71028444.15053315,\n \"symbol\": \"deserunt officia dolore\",\n \"time\": \"magna occaecat est\",\n \"user_id\": -71272721\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/transaction", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "transaction" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": {\n \"value\": \"reference #/components/schemas/() not found in the OpenAPI spec\"\n },\n \"status\": 14310780,\n \"text\": \"in sint sit\"\n}" + }, + { + "name": "Validation error", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"count\": 96634001,\n \"price\": -71028444.15053315,\n \"symbol\": \"deserunt officia dolore\",\n \"time\": \"magna occaecat est\",\n \"user_id\": -71272721\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/transaction", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "transaction" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"count\": 96634001,\n \"price\": -71028444.15053315,\n \"symbol\": \"deserunt officia dolore\",\n \"time\": \"magna occaecat est\",\n \"user_id\": -71272721\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/transaction", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "transaction" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + } + ] + }, + { + "name": "Returns all transactions", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/api/transactions", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "transactions" + ] + }, + "description": "Returns all transactions for current user" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/transactions", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "transactions" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": {\n \"count\": -19053100,\n \"price\": 39909986.26075193,\n \"symbol\": \"sit nisi\",\n \"time\": \"pariatur eu proident\",\n \"user_id\": 54782742\n },\n \"status\": 87070947,\n \"text\": \"null\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/transactions", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "transactions" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + } + ] } ] }, @@ -339,61 +1219,1092 @@ "name": "Portfolio", "item": [ { - "name": "/api/portfolio", + "name": "Returns portfolio", "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, "method": "GET", - "header": [], + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], "url": { - "raw": "{{BASE_URL}}/api/portfolio", + "raw": "{{baseUrl}}/api/portfolio", "host": [ - "{{BASE_URL}}" + "{{baseUrl}}" ], "path": [ "api", "portfolio" ] - } + }, + "description": "Returns all shares of current user" }, - "response": [] + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/portfolio", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "portfolio" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": {\n \"value\": \"reference #/components/schemas/200 not found in the OpenAPI spec\"\n },\n \"status\": 14426134,\n \"text\": \"velit in ad dolore\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/portfolio", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "portfolio" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + } + ] + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "Delete user", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"aliqua dolor do\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user" + ] + }, + "description": "Deletes user by username" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"aliqua dolor do\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user" + ] + } + }, + "status": "No Content", + "code": 204, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "cookie": [], + "body": "" + }, + { + "name": "Validation error", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"aliqua dolor do\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"cupidatat mollit laborum aute\",\n \"aute in laboris dolor\"\n ]\n }\n },\n \"message\": \"in dolore exercitation eu elit\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "DELETE", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"aliqua dolor do\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + } + ] + }, + { + "name": "Get current user", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/api/user", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user" + ] + }, + "description": "Returns current user" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/user", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": {\n \"admin\": false,\n \"password\": \"voluptate magna esse\",\n \"telegram_name\": \"id\",\n \"user_id\": -55336867,\n \"username\": \"do exercitation\"\n },\n \"status\": -11469710,\n \"text\": \"aute\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/user", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + } + ] + }, + { + "name": "Update user", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user" + ] + }, + "description": "Changes password and/or username of current user" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "PUT", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user" + ] + } + }, + "status": "No Content", + "code": 204, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "cookie": [], + "body": "" + }, + { + "name": "Validation error", + "originalRequest": { + "method": "PUT", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "PUT", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + } + ] + }, + { + "name": "Login", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/login", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "login" + ] + }, + "description": "Returns jwt token if username and password match, otherwise returns error" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/login", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "login" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": {\n \"token\": \"minim\"\n },\n \"status\": 86563099,\n \"text\": \"non tempor quis ullamco est\"\n}" + }, + { + "name": "Validation error", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/login", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "login" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + } + ] + }, + { + "name": "Register", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/register", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "register" + ] + }, + "description": "Registers user" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/register", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "register" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": {\n \"admin\": false,\n \"password\": \"voluptate magna esse\",\n \"telegram_name\": \"id\",\n \"user_id\": -55336867,\n \"username\": \"do exercitation\"\n },\n \"status\": -11469710,\n \"text\": \"aute\"\n}" + }, + { + "name": "Validation error", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/register", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "register" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + } + ] + }, + { + "name": "Set user admin state", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"admin\": true,\n \"username\": \"sunt\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/setAdmin", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "setAdmin" + ] + }, + "description": "Set admin state of specified user" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "PUT", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"admin\": true,\n \"username\": \"sunt\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/setAdmin", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "setAdmin" + ] + } + }, + "status": "No Content", + "code": 204, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "cookie": [], + "body": "" + }, + { + "name": "Validation error", + "originalRequest": { + "method": "PUT", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"admin\": true,\n \"username\": \"sunt\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/setAdmin", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "setAdmin" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "PUT", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"admin\": true,\n \"username\": \"sunt\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/setAdmin", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "setAdmin" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + } + ] + }, + { + "name": "Get all users", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/api/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "users" + ] + }, + "description": "Returns all existing users as array" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "users" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": [\n {\n \"admin\": true,\n \"password\": \"incididunt id dolore\",\n \"telegram_name\": \"id sed\",\n \"user_id\": 15906508,\n \"username\": \"dolor consequat ullamco\"\n },\n {\n \"admin\": true,\n \"password\": \"voluptate non\",\n \"telegram_name\": \"dolor sunt fugiat exercitation\",\n \"user_id\": 95246024,\n \"username\": \"Duis qui culpa Ut labore\"\n }\n ],\n \"status\": 81180129,\n \"text\": \"in ullamco\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "users" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + } + ] } ] } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IlVzZXJuYW1lIiwiZXhwIjoxNjQ3Mjk5MzIxfQ.5UoHi9Zu6p9szSVKK2_1Ln2uru4RVTGQl0MyHDB4sqg", - "type": "string" - } - ] - }, - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ], "variable": [ { - "key": "BASE_URL", - "value": "https://aktienbot.flokaiser.com/", - "type": "default" + "key": "baseUrl", + "value": "https://aktienbot.flokaiser.com", + "type": "string" } ] } \ No newline at end of file From 135206b3ad12f1748ff1fe58fe85b036eb598d15 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Thu, 17 Mar 2022 23:18:36 +0100 Subject: [PATCH 013/263] Updated README.md --- documentation/README.md | 20 ++++++++++++++++++++ documentation/database/README.md | 1 - 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 documentation/README.md delete mode 100644 documentation/database/README.md diff --git a/documentation/README.md b/documentation/README.md new file mode 100644 index 0000000..bce18b4 --- /dev/null +++ b/documentation/README.md @@ -0,0 +1,20 @@ +# Dokumentation + +## API +- `api/openapi.json` + - OpenAPI-Dokumentation + - von APIFlask generiert + - kann in Postman importiert werden +- `api/postman.json` + - Postman Collection + +## Database +- `database/structure_database.pdf` + - Relationales Modell der Datenbank +- `database/structure_database.uxf` + - Relationales Modell der Datenbank + - Source Datei des Modells + +## Requirements +- `requirements/2022-03-15 Anforderungen an Projekt.pdf` + - Anforderungen an dieses Projekt \ No newline at end of file diff --git a/documentation/database/README.md b/documentation/database/README.md deleted file mode 100644 index 26568bd..0000000 --- a/documentation/database/README.md +++ /dev/null @@ -1 +0,0 @@ -This is the database folder From d13ba5f5efce1881075b75d403def5c3ae8b2a58 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Thu, 17 Mar 2022 23:49:39 +0100 Subject: [PATCH 014/263] Updated README files, .gitignore and .env.example --- .gitignore | 1 - .env.example => api/.env.example | 4 ---- api/README.md | 41 ++++++++++++++++++++++++++++++++ telegram_bot/.env.example | 8 +++++++ telegram_bot/README.md | 39 +++++++++++++++++++++++++----- 5 files changed, 82 insertions(+), 11 deletions(-) rename .env.example => api/.env.example (68%) create mode 100644 api/README.md create mode 100644 telegram_bot/.env.example diff --git a/.gitignore b/.gitignore index fba0141..ac68c97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .idea/* .env -.env.example env Lib Include diff --git a/.env.example b/api/.env.example similarity index 68% rename from .env.example rename to api/.env.example index 921ca00..af67b58 100644 --- a/.env.example +++ b/api/.env.example @@ -5,9 +5,5 @@ MYSQL_DATABASE= MYSQL_USER= MYSQL_PASSWORD= -# Telegram bot api key -BOT_API_KEY="" -NEWS_API_KEY="" - # Flask secret key SECRET_KEY= diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..e771473 --- /dev/null +++ b/api/README.md @@ -0,0 +1,41 @@ +# API + +Aktienbot API + +## Development +1. Create virtual environment `python -m venv venv env/Scripts/activate` +2. Install requirements `pip install -r api/requirements.txt` +3. Set environment variables (see list below) + 1. Use `.env`-file in `api` directory like `.env.example` + 2. Or set variables using `export` or `set` commands. (Windows `set`, Linux `export`) +4. Run api `python api/app.py` + +## Environment variables +``` + # Flask secret key + SECRET_KEY= + + # MYSQL Connection + MYSQL_USER= + MYSQL_PASSWORD= + MYSQL_HOST= + MYSQL_PORT= + MYSQL_DATABASE= +``` + +## Docker +``` +docker run -d \ + --name aktienbot_api \ + --hostname aktienbot_api \ + --publish 80:80 \ + --env "SECRET_KEY=" \ + --env "MYSQL_USER=" \ + --env "MYSQL_PASSWORD=" \ + --env "MYSQL_HOST=" \ + --env "MYSQL_PORT=" \ + --env "MYSQL_DATABASE=" \ + --restart unless-stopped \ + registry.flokaiser.com/aktienbot/api:latest +``` +or load environment variables from file by using `--env-file ` \ No newline at end of file diff --git a/telegram_bot/.env.example b/telegram_bot/.env.example new file mode 100644 index 0000000..bcd6a02 --- /dev/null +++ b/telegram_bot/.env.example @@ -0,0 +1,8 @@ +# Telegram bot api key +BOT_API_KEY= + +# News api key +NEWS_API_KEY= + +# Flask secret key +SECRET_KEY= diff --git a/telegram_bot/README.md b/telegram_bot/README.md index abda9e2..05ac44d 100644 --- a/telegram_bot/README.md +++ b/telegram_bot/README.md @@ -1,6 +1,33 @@ -## Local setup for telegram bot -0. optional: build virtual env by ``python -m venv venv`` - ``env/Scripts/activate`` -2. create .env and set API keys etc. (use .env.example as a layout) -3. install required libs via ``pip install -r ./telegram_bot/requirements.txt`` -4. run bot.py via ``python ./telegram_bot/bot.py`` +# Telegram bot + +Aktienbot telegram bot + +## Development +1. Create virtual environment `python -m venv venv env/Scripts/activate` +2. Install requirements `pip install -r telegram_bot/requirements.txt` +3. Set environment variables (see list below) + 1. Use `.env`-file in `api` directory like `.env.example` + 2. Or set variables using `export` or `set` commands. (Windows `set`, Linux `export`) +4. Run api `python telegram_bot/bot.py` + +## Environment variables +``` + # Telegram bot api key + BOT_API_KEY= + + # News api key + NEWS_API_KEY= +``` + +## Docker +``` +docker run -d \ + --name aktienbot_bot \ + --hostname aktienbot_bot \ + --publish 80:80 \ + --env "BOT_API_KEY=" \ + --env "NEWS_API_KEY=" \ + --restart unless-stopped \ + registry.flokaiser.com/aktienbot/bot:latest +``` +or load environment variables from file by using `--env-file ` \ No newline at end of file From 5dd17c7cf9b2f1efeeb18d1839fbde00291af8f3 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Fri, 18 Mar 2022 00:06:24 +0100 Subject: [PATCH 015/263] Updated README files, .gitignore and .env.example --- documentation/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/documentation/README.md b/documentation/README.md index bce18b4..b575c73 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -17,4 +17,12 @@ ## Requirements - `requirements/2022-03-15 Anforderungen an Projekt.pdf` - - Anforderungen an dieses Projekt \ No newline at end of file + - Anforderungen an dieses Projekt + +## Role-model +Function|Admin|No Admin| +|---|---|---| +|Delete user|yes|only current user| +|Update users|yes|only current user| +|Set admin state|yes|no| +|Show all users|yes|no| \ No newline at end of file From 675dd242ec58891475bbd084035928cd1d939151 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 18 Mar 2022 00:13:09 +0100 Subject: [PATCH 016/263] Create dependabot.yml --- .github/dependabot.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..96a2e95 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,24 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # API + - package-ecosystem: "pip" + directory: "/api" + schedule: + interval: "daily" + + # Bot + - package-ecosystem: "pip" + directory: "/telegram_bot" + schedule: + interval: "daily" + + # Frontend + - package-ecosystem: "npm" + directory: "/frontend" + schedule: + interval: "daily" From cc7aceb2fc65ab5c61d6d1f089de79d441ef7537 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Mar 2022 23:13:30 +0000 Subject: [PATCH 017/263] Bump pyjwt from 2.0.0 to 2.3.0 in /api Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.0.0 to 2.3.0. - [Release notes](https://github.com/jpadilla/pyjwt/releases) - [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst) - [Commits](https://github.com/jpadilla/pyjwt/compare/2.0.0...2.3.0) --- updated-dependencies: - dependency-name: pyjwt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- api/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/requirements.txt b/api/requirements.txt index d37bc8e..91de9c7 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -5,7 +5,7 @@ uwsgi==2.0.20 Flask_SQLAlchemy==2.5.1 python-dotenv==0.19.2 pymysql==1.0.2 -pyjwt==2.0.0 +pyjwt==2.3.0 apiflask==0.12.0 flask-swagger-ui==3.36.0 flask-cors==3.0.10 From ffe58e13b98247e9f5ca9a74a376690335f705ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Mar 2022 23:14:16 +0000 Subject: [PATCH 018/263] Bump @angular/material from 13.2.6 to 13.3.0 in /frontend Bumps [@angular/material](https://github.com/angular/components) from 13.2.6 to 13.3.0. - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/13.2.6...13.3.0) --- updated-dependencies: - dependency-name: "@angular/material" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 28 ++++++++++++++-------------- frontend/package.json | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c412349..22c8c14 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,7 +14,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.2.6", + "@angular/material": "^13.3.0", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", @@ -345,9 +345,9 @@ } }, "node_modules/@angular/cdk": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.2.6.tgz", - "integrity": "sha512-epuXmaHqfukwPsYvIksbuHLXDtb6GALV2Vgv6W2asj4TorD584CeQTs0EcdPGmCzhGUYI8U8QV63WOxu9YFcNA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.0.tgz", + "integrity": "sha512-TyTcs9/Vd0TyQIjRf40xCDrTBK2GGCUrrHCVuHTXIqL8yvaGRnz815fL9X3hSZySCcbRV4NeK1yWeiZ9jf4Vxw==", "dependencies": { "tslib": "^2.3.0" }, @@ -577,15 +577,15 @@ } }, "node_modules/@angular/material": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.2.6.tgz", - "integrity": "sha512-/h5wa/tXE0DMIIEQX+rozFkUWHYUWg1Xf1R2tXUFLslLQ0KRCGyNo225Sv/1wrxXHxfrML787lA9ex4p90Feqw==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.0.tgz", + "integrity": "sha512-g9WRuzUo0HPeEZKQi3uRZU3lfFwcdVqdXL8F9vxpR6fg0u9jNF+Wm+sIlsADS93h/zS0IZXYecge0NU8T7Kd0g==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "^13.0.0 || ^14.0.0-0", - "@angular/cdk": "13.2.6", + "@angular/cdk": "13.3.0", "@angular/common": "^13.0.0 || ^14.0.0-0", "@angular/core": "^13.0.0 || ^14.0.0-0", "@angular/forms": "^13.0.0 || ^14.0.0-0", @@ -11671,9 +11671,9 @@ } }, "@angular/cdk": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.2.6.tgz", - "integrity": "sha512-epuXmaHqfukwPsYvIksbuHLXDtb6GALV2Vgv6W2asj4TorD584CeQTs0EcdPGmCzhGUYI8U8QV63WOxu9YFcNA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.0.tgz", + "integrity": "sha512-TyTcs9/Vd0TyQIjRf40xCDrTBK2GGCUrrHCVuHTXIqL8yvaGRnz815fL9X3hSZySCcbRV4NeK1yWeiZ9jf4Vxw==", "requires": { "parse5": "^5.0.0", "tslib": "^2.3.0" @@ -11833,9 +11833,9 @@ } }, "@angular/material": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.2.6.tgz", - "integrity": "sha512-/h5wa/tXE0DMIIEQX+rozFkUWHYUWg1Xf1R2tXUFLslLQ0KRCGyNo225Sv/1wrxXHxfrML787lA9ex4p90Feqw==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.0.tgz", + "integrity": "sha512-g9WRuzUo0HPeEZKQi3uRZU3lfFwcdVqdXL8F9vxpR6fg0u9jNF+Wm+sIlsADS93h/zS0IZXYecge0NU8T7Kd0g==", "requires": { "tslib": "^2.3.0" } diff --git a/frontend/package.json b/frontend/package.json index 488a7aa..94d319a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.2.6", + "@angular/material": "^13.3.0", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", From f3b028e2aa8b990ae3fd2a7b980095d947602fee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Mar 2022 23:15:11 +0000 Subject: [PATCH 019/263] Bump @types/jasmine from 3.10.3 to 4.0.0 in /frontend Bumps [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) from 3.10.3 to 4.0.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine) --- updated-dependencies: - dependency-name: "@types/jasmine" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 14 +++++++------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c412349..7bb67ac 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -27,7 +27,7 @@ "@angular-devkit/build-angular": "~13.2.5", "@angular/cli": "~13.2.5", "@angular/compiler-cli": "~13.2.0", - "@types/jasmine": "~3.10.0", + "@types/jasmine": "~4.0.0", "@types/node": "^12.11.1", "jasmine-core": "~4.0.0", "karma": "~6.3.0", @@ -2712,9 +2712,9 @@ } }, "node_modules/@types/jasmine": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.10.3.tgz", - "integrity": "sha512-SWyMrjgdAUHNQmutvDcKablrJhkDLy4wunTme8oYLjKp41GnHGxMRXr2MQMvy/qy8H3LdzwQk9gH4hZ6T++H8g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.0.tgz", + "integrity": "sha512-KvhqNz4NaONk7cfp4E9x+uXOUp7x4H2Zeyb4yXnw2vIuxD5YfSi1767x+aF7z54elhZcC0OH9/78/WL6+5jcDg==", "dev": true }, "node_modules/@types/json-schema": { @@ -13367,9 +13367,9 @@ } }, "@types/jasmine": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.10.3.tgz", - "integrity": "sha512-SWyMrjgdAUHNQmutvDcKablrJhkDLy4wunTme8oYLjKp41GnHGxMRXr2MQMvy/qy8H3LdzwQk9gH4hZ6T++H8g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.0.tgz", + "integrity": "sha512-KvhqNz4NaONk7cfp4E9x+uXOUp7x4H2Zeyb4yXnw2vIuxD5YfSi1767x+aF7z54elhZcC0OH9/78/WL6+5jcDg==", "dev": true }, "@types/json-schema": { diff --git a/frontend/package.json b/frontend/package.json index 488a7aa..ec2c672 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,7 +29,7 @@ "@angular-devkit/build-angular": "~13.2.5", "@angular/cli": "~13.2.5", "@angular/compiler-cli": "~13.2.0", - "@types/jasmine": "~3.10.0", + "@types/jasmine": "~4.0.0", "@types/node": "^12.11.1", "jasmine-core": "~4.0.0", "karma": "~6.3.0", From 9e7042984cdc663a67faca8eaa18f502010acaac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Mar 2022 23:15:30 +0000 Subject: [PATCH 020/263] Bump karma-coverage from 2.1.1 to 2.2.0 in /frontend Bumps [karma-coverage](https://github.com/karma-runner/karma-coverage) from 2.1.1 to 2.2.0. - [Release notes](https://github.com/karma-runner/karma-coverage/releases) - [Changelog](https://github.com/karma-runner/karma-coverage/blob/master/CHANGELOG.md) - [Commits](https://github.com/karma-runner/karma-coverage/compare/v2.1.1...v2.2.0) --- updated-dependencies: - dependency-name: karma-coverage dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 62 ++++++-------------------------------- frontend/package.json | 2 +- 2 files changed, 10 insertions(+), 54 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c412349..0acb540 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -32,7 +32,7 @@ "jasmine-core": "~4.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", - "karma-coverage": "~2.1.0", + "karma-coverage": "~2.2.0", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "~1.7.0", "typescript": "~4.5.2" @@ -7002,13 +7002,13 @@ } }, "node_modules/karma-coverage": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.1.1.tgz", - "integrity": "sha512-oxeOSBVK/jdZsiX03LhHQkO4eISSQb5GbHi6Nsw3Mw7G4u6yUgacBAftnO7q+emPBLMsrNbz1pGIrj+Jb3z17A==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.0.tgz", + "integrity": "sha512-gPVdoZBNDZ08UCzdMHHhEImKrw1+PAOQOIiffv1YsvxFhBjqvo/SVXNk4tqn1SYqX0BJZT6S/59zgxiBe+9OuA==", "dev": true, "dependencies": { "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-instrument": "^5.1.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.1", "istanbul-reports": "^3.0.5", @@ -7018,30 +7018,6 @@ "node": ">=10.0.0" } }, - "node_modules/karma-coverage/node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/karma-coverage/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/karma-jasmine": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-4.0.1.tgz", @@ -16615,37 +16591,17 @@ } }, "karma-coverage": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.1.1.tgz", - "integrity": "sha512-oxeOSBVK/jdZsiX03LhHQkO4eISSQb5GbHi6Nsw3Mw7G4u6yUgacBAftnO7q+emPBLMsrNbz1pGIrj+Jb3z17A==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.0.tgz", + "integrity": "sha512-gPVdoZBNDZ08UCzdMHHhEImKrw1+PAOQOIiffv1YsvxFhBjqvo/SVXNk4tqn1SYqX0BJZT6S/59zgxiBe+9OuA==", "dev": true, "requires": { "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-instrument": "^4.0.3", + "istanbul-lib-instrument": "^5.1.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.1", "istanbul-reports": "^3.0.5", "minimatch": "^3.0.4" - }, - "dependencies": { - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } } }, "karma-jasmine": { diff --git a/frontend/package.json b/frontend/package.json index 488a7aa..7818bfe 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,7 +34,7 @@ "jasmine-core": "~4.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", - "karma-coverage": "~2.1.0", + "karma-coverage": "~2.2.0", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "~1.7.0", "typescript": "~4.5.2" From d3fbc590658174ebe037f9dd76b36567bb800d37 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 18 Mar 2022 00:25:27 +0100 Subject: [PATCH 021/263] Update dependabot.yml --- .github/dependabot.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 96a2e95..0cba6af 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,15 +10,26 @@ updates: directory: "/api" schedule: interval: "daily" + assignees: + - "H4CK3R-01" + open-pull-requests-limit: 0 # Bot - package-ecosystem: "pip" directory: "/telegram_bot" schedule: interval: "daily" + assignees: + - "NormalParameter" + - "Rripped" + - "FlorianKellermann" + open-pull-requests-limit: 0 # Frontend - - package-ecosystem: "npm" - directory: "/frontend" - schedule: - interval: "daily" + # - package-ecosystem: "npm" + # directory: "/frontend" + # schedule: + # interval: "daily" + # assignees: + # - "kevinpauer" + # open-pull-requests-limit: 0 From 9ef33fc38a0223393b37896f7bed85890b57ef03 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 18 Mar 2022 00:26:23 +0100 Subject: [PATCH 022/263] Update dependabot.yml --- .github/dependabot.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0cba6af..89a1b72 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -26,10 +26,10 @@ updates: open-pull-requests-limit: 0 # Frontend - # - package-ecosystem: "npm" - # directory: "/frontend" - # schedule: - # interval: "daily" - # assignees: - # - "kevinpauer" - # open-pull-requests-limit: 0 + - package-ecosystem: "npm" + directory: "/frontend" + schedule: + interval: "daily" + assignees: + - "kevinpauer" + open-pull-requests-limit: 0 From 2be454c0c56b3c2021bfeae1fc10305c16af4604 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 18 Mar 2022 00:26:53 +0100 Subject: [PATCH 023/263] Update dependabot.yml --- .github/dependabot.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 89a1b72..cd66a59 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -27,9 +27,9 @@ updates: # Frontend - package-ecosystem: "npm" - directory: "/frontend" - schedule: - interval: "daily" - assignees: - - "kevinpauer" - open-pull-requests-limit: 0 + directory: "/frontend" + schedule: + interval: "daily" + assignees: + - "kevinpauer" + open-pull-requests-limit: 0 From fc993c0138e2e1bda45522ed50bba6c772f301da Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 18 Mar 2022 00:31:50 +0100 Subject: [PATCH 024/263] Update dependabot.yml --- .github/dependabot.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cd66a59..76ca8b1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,7 +12,7 @@ updates: interval: "daily" assignees: - "H4CK3R-01" - open-pull-requests-limit: 0 + open-pull-requests-limit: 100 # Bot - package-ecosystem: "pip" @@ -23,7 +23,7 @@ updates: - "NormalParameter" - "Rripped" - "FlorianKellermann" - open-pull-requests-limit: 0 + open-pull-requests-limit: 100 # Frontend - package-ecosystem: "npm" @@ -32,4 +32,4 @@ updates: interval: "daily" assignees: - "kevinpauer" - open-pull-requests-limit: 0 + open-pull-requests-limit: 100 From 5d14c6a97dc483e82edb1eec7b216f088a2ffcfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Mar 2022 23:33:41 +0000 Subject: [PATCH 025/263] Bump @angular/cli from 13.2.6 to 13.3.0 in /frontend Bumps [@angular/cli](https://github.com/angular/angular-cli) from 13.2.6 to 13.3.0. - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/13.2.6...13.3.0) --- updated-dependencies: - dependency-name: "@angular/cli" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 284 ++++++++++++++++++++++++++++++++----- frontend/package.json | 2 +- 2 files changed, 252 insertions(+), 34 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c412349..f88e4aa 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,7 +25,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "~13.2.5", - "@angular/cli": "~13.2.5", + "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~3.10.0", "@types/node": "^12.11.1", @@ -295,12 +295,12 @@ "dev": true }, "node_modules/@angular-devkit/schematics": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.2.6.tgz", - "integrity": "sha512-mPgSqdnZRuPSMeUA+T+mwVCrq2yhXpcYm1/Rjbhy09CyHs4wSrFv21WHCrE6shlvXpcmwr0n+I0DIeagAPmjUA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.0.tgz", + "integrity": "sha512-hq7tqnB3uVT/iDgqWWZ4kvnijeAcgd4cfLzZiCPaYn1nuhZf0tWsho6exhJ/odMZHvVp7w8OibqWiUKxNY9zHA==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.2.6", + "@angular-devkit/core": "13.3.0", "jsonc-parser": "3.0.0", "magic-string": "0.25.7", "ora": "5.4.1", @@ -312,6 +312,33 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/schematics/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -367,16 +394,16 @@ "optional": true }, "node_modules/@angular/cli": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.2.6.tgz", - "integrity": "sha512-xIjEaQI5sWemXXc7GXLm4u9UL5sjtrQL/y1PJvvk/Jsa8+kIT+MutOfZfC7zcdAh9fqHd8mokH3guFV8BJdFxA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.0.tgz", + "integrity": "sha512-2qCKP/QsyxrJnpd3g4P/iTQ4TjI04N8r+bG5YLLfudoMDsQ/Ti4ogdI7PBeG2IMbRylZW9XLjHraWG42+Y9tWw==", "dev": true, "hasInstallScript": true, "dependencies": { - "@angular-devkit/architect": "0.1302.6", - "@angular-devkit/core": "13.2.6", - "@angular-devkit/schematics": "13.2.6", - "@schematics/angular": "13.2.6", + "@angular-devkit/architect": "0.1303.0", + "@angular-devkit/core": "13.3.0", + "@angular-devkit/schematics": "13.3.0", + "@schematics/angular": "13.3.0", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -402,6 +429,66 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { + "version": "0.1303.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.0.tgz", + "integrity": "sha512-kTcKB917ICA8j53SGo4gn+qAlzx8si+iHnOTbp5QlMr7qt/Iz07SVVI8mRlMD6c6lr7eE/fVlCLzEZ1+WCQpTA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.0", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/@angular-devkit/core": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular/cli/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular/cli/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/@angular/common": { "version": "13.2.6", "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.2.6.tgz", @@ -2564,13 +2651,13 @@ } }, "node_modules/@schematics/angular": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.2.6.tgz", - "integrity": "sha512-8NzHMX9+FSgaB0lJYxlTJv9OcBuolwZJqo9M/yX3RPSqSHghA33jWwgVbV551hBJOpbVEePerG1DQkIC99DXKA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.0.tgz", + "integrity": "sha512-WND6DXWf0ZFefqlC2hUm1FzHDonRfGpDEPWVhVulhYkB7IUUaXuCz8K41HAScyJ3bxUngs2Lx9+4omikc05fxA==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.2.6", - "@angular-devkit/schematics": "13.2.6", + "@angular-devkit/core": "13.3.0", + "@angular-devkit/schematics": "13.3.0", "jsonc-parser": "3.0.0" }, "engines": { @@ -2579,6 +2666,51 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@schematics/angular/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@schematics/angular/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/@socket.io/base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", @@ -11633,18 +11765,32 @@ } }, "@angular-devkit/schematics": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.2.6.tgz", - "integrity": "sha512-mPgSqdnZRuPSMeUA+T+mwVCrq2yhXpcYm1/Rjbhy09CyHs4wSrFv21WHCrE6shlvXpcmwr0n+I0DIeagAPmjUA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.0.tgz", + "integrity": "sha512-hq7tqnB3uVT/iDgqWWZ4kvnijeAcgd4cfLzZiCPaYn1nuhZf0tWsho6exhJ/odMZHvVp7w8OibqWiUKxNY9zHA==", "dev": true, "requires": { - "@angular-devkit/core": "13.2.6", + "@angular-devkit/core": "13.3.0", "jsonc-parser": "3.0.0", "magic-string": "0.25.7", "ora": "5.4.1", "rxjs": "6.6.7" }, "dependencies": { + "@angular-devkit/core": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -11688,15 +11834,15 @@ } }, "@angular/cli": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.2.6.tgz", - "integrity": "sha512-xIjEaQI5sWemXXc7GXLm4u9UL5sjtrQL/y1PJvvk/Jsa8+kIT+MutOfZfC7zcdAh9fqHd8mokH3guFV8BJdFxA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.0.tgz", + "integrity": "sha512-2qCKP/QsyxrJnpd3g4P/iTQ4TjI04N8r+bG5YLLfudoMDsQ/Ti4ogdI7PBeG2IMbRylZW9XLjHraWG42+Y9tWw==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1302.6", - "@angular-devkit/core": "13.2.6", - "@angular-devkit/schematics": "13.2.6", - "@schematics/angular": "13.2.6", + "@angular-devkit/architect": "0.1303.0", + "@angular-devkit/core": "13.3.0", + "@angular-devkit/schematics": "13.3.0", + "@schematics/angular": "13.3.0", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -11712,6 +11858,47 @@ "semver": "7.3.5", "symbol-observable": "4.0.0", "uuid": "8.3.2" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.1303.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.0.tgz", + "integrity": "sha512-kTcKB917ICA8j53SGo4gn+qAlzx8si+iHnOTbp5QlMr7qt/Iz07SVVI8mRlMD6c6lr7eE/fVlCLzEZ1+WCQpTA==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.0", + "rxjs": "6.6.7" + } + }, + "@angular-devkit/core": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "@angular/common": { @@ -13230,14 +13417,45 @@ "peer": true }, "@schematics/angular": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.2.6.tgz", - "integrity": "sha512-8NzHMX9+FSgaB0lJYxlTJv9OcBuolwZJqo9M/yX3RPSqSHghA33jWwgVbV551hBJOpbVEePerG1DQkIC99DXKA==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.0.tgz", + "integrity": "sha512-WND6DXWf0ZFefqlC2hUm1FzHDonRfGpDEPWVhVulhYkB7IUUaXuCz8K41HAScyJ3bxUngs2Lx9+4omikc05fxA==", "dev": true, "requires": { - "@angular-devkit/core": "13.2.6", - "@angular-devkit/schematics": "13.2.6", + "@angular-devkit/core": "13.3.0", + "@angular-devkit/schematics": "13.3.0", "jsonc-parser": "3.0.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "@socket.io/base64-arraybuffer": { diff --git a/frontend/package.json b/frontend/package.json index 488a7aa..94ba1f8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,7 +27,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "~13.2.5", - "@angular/cli": "~13.2.5", + "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~3.10.0", "@types/node": "^12.11.1", From e8137cada10ea1bd0029cb034ef3897acf4b437c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Mar 2022 12:05:47 +0000 Subject: [PATCH 026/263] Bump @types/node from 12.20.47 to 17.0.21 in /frontend Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 12.20.47 to 17.0.21. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 14 +++++++------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5044f2f..f6df079 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,7 +28,7 @@ "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", - "@types/node": "^12.11.1", + "@types/node": "^17.0.21", "jasmine-core": "~4.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2862,9 +2862,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "12.20.47", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.47.tgz", - "integrity": "sha512-BzcaRsnFuznzOItW1WpQrDHM7plAa7GIDMZ6b5pnMbkqEtM/6WCOhvZar39oeMQP79gwvFUWjjptE7/KGcNqFg==", + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", "dev": true }, "node_modules/@types/parse-json": { @@ -13579,9 +13579,9 @@ "dev": true }, "@types/node": { - "version": "12.20.47", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.47.tgz", - "integrity": "sha512-BzcaRsnFuznzOItW1WpQrDHM7plAa7GIDMZ6b5pnMbkqEtM/6WCOhvZar39oeMQP79gwvFUWjjptE7/KGcNqFg==", + "version": "17.0.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", + "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", "dev": true }, "@types/parse-json": { diff --git a/frontend/package.json b/frontend/package.json index a0e8455..2371578 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,7 @@ "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", - "@types/node": "^12.11.1", + "@types/node": "^17.0.21", "jasmine-core": "~4.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", From f11b50d6cbe4b23a5ae1cf5a46b7fe8973c22a7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Mar 2022 12:06:18 +0000 Subject: [PATCH 027/263] Bump @angular-devkit/build-angular from 13.2.6 to 13.3.0 in /frontend Bumps [@angular-devkit/build-angular](https://github.com/angular/angular-cli) from 13.2.6 to 13.3.0. - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/13.2.6...13.3.0) --- updated-dependencies: - dependency-name: "@angular-devkit/build-angular" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 366 ++++++++----------------------------- frontend/package.json | 2 +- 2 files changed, 75 insertions(+), 293 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5044f2f..a8efd8a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,7 +24,7 @@ "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.2.5", + "@angular-devkit/build-angular": "~13.3.0", "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", @@ -52,12 +52,12 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1302.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1302.6.tgz", - "integrity": "sha512-NztzorUMfwJeRaT7SY00Y8WSqc2lQYuF11yNoyEm7Dae3V7VZ28rW2Z9RwibP27rYQL0RjSMaz2wKITHX2vOAw==", + "version": "0.1303.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.0.tgz", + "integrity": "sha512-kTcKB917ICA8j53SGo4gn+qAlzx8si+iHnOTbp5QlMr7qt/Iz07SVVI8mRlMD6c6lr7eE/fVlCLzEZ1+WCQpTA==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.2.6", + "@angular-devkit/core": "13.3.0", "rxjs": "6.6.7" }, "engines": { @@ -85,15 +85,15 @@ "dev": true }, "node_modules/@angular-devkit/build-angular": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.2.6.tgz", - "integrity": "sha512-Y2ojy6xbZ0kwScppcutLHBP8eW0qNOjburTISSBU/L5l/9FOeZ1E7yAreKuVu/qibZiLbSJfAhk+SLwhRHFSSQ==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.0.tgz", + "integrity": "sha512-3Ji7EeqGHj7i1Jgmeo3aIEXsnfKyFeQPpl65gcYmHwj5dP4lZzLSU4rMaWWUKksccgqCUXgPI2vKePTPazmikg==", "dev": true, "dependencies": { "@ampproject/remapping": "1.1.1", - "@angular-devkit/architect": "0.1302.6", - "@angular-devkit/build-webpack": "0.1302.6", - "@angular-devkit/core": "13.2.6", + "@angular-devkit/architect": "0.1303.0", + "@angular-devkit/build-webpack": "0.1303.0", + "@angular-devkit/core": "13.3.0", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -104,7 +104,7 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.2.6", + "@ngtools/webpack": "13.3.0", "ansi-colors": "4.1.1", "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", @@ -149,7 +149,7 @@ "text-table": "0.2.0", "tree-kill": "1.2.2", "tslib": "2.3.1", - "webpack": "5.67.0", + "webpack": "5.70.0", "webpack-dev-middleware": "5.3.0", "webpack-dev-server": "4.7.3", "webpack-merge": "5.8.0", @@ -164,14 +164,14 @@ "esbuild": "0.14.22" }, "peerDependencies": { - "@angular/compiler-cli": "^13.0.0", - "@angular/localize": "^13.0.0", - "@angular/service-worker": "^13.0.0", + "@angular/compiler-cli": "^13.0.0 || ^13.3.0-rc.0", + "@angular/localize": "^13.0.0 || ^13.3.0-rc.0", + "@angular/service-worker": "^13.0.0 || ^13.3.0-rc.0", "karma": "^6.3.0", "ng-packagr": "^13.0.0", "protractor": "^7.0.0", "tailwindcss": "^2.0.0 || ^3.0.0", - "typescript": ">=4.4.3 <4.6" + "typescript": ">=4.4.3 <4.7" }, "peerDependenciesMeta": { "@angular/localize": { @@ -213,12 +213,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1302.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1302.6.tgz", - "integrity": "sha512-TYEh2n9tPe932rEIgdiSpojOqtDppW2jzb/empVqCkLF7WUZsXKvTanttZC34L6R2VD6SAGWhb6JDg75ghUVYA==", + "version": "0.1303.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.0.tgz", + "integrity": "sha512-a+Veg2oYn3RM2Kl148BReuONmD1kjbbYBnMUVi8nD6rvJPStFZkqN5s5ZkYybKeWnzMGaB3VasKR88z5XeH22A==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1302.6", + "@angular-devkit/architect": "0.1303.0", "rxjs": "6.6.7" }, "engines": { @@ -250,9 +250,9 @@ "dev": true }, "node_modules/@angular-devkit/core": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.2.6.tgz", - "integrity": "sha512-8h2mWdBTN/dYwZuzKMg2IODlOWMdbJcpQG4XVrkk9ejCPP+3aX5Aa3glCe/voN6eBNiRfs8YDM0jxmpN2aWVtg==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", "dev": true, "dependencies": { "ajv": "8.9.0", @@ -312,33 +312,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", - "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", - "dev": true, - "dependencies": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, "node_modules/@angular-devkit/schematics/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -429,66 +402,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.1303.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.0.tgz", - "integrity": "sha512-kTcKB917ICA8j53SGo4gn+qAlzx8si+iHnOTbp5QlMr7qt/Iz07SVVI8mRlMD6c6lr7eE/fVlCLzEZ1+WCQpTA==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "13.3.0", - "rxjs": "6.6.7" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", - "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", - "dev": true, - "dependencies": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular/cli/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular/cli/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular/common": { "version": "13.2.6", "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.2.6.tgz", @@ -2493,9 +2406,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.2.6.tgz", - "integrity": "sha512-N8SvRV91+/57TcAfbghc0k0tKCukw/7KqbDaLPAQTGFekJ4xMGT3elMzOyBXTH3Hvp5HL8/hiBt2tG04qiMf+w==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.0.tgz", + "integrity": "sha512-QbTQWXK2WzYU+aKKVDG0ya7WYT+6rNAUXVt5ov9Nz1SGgDeozpiOx8ZqPWUvnToTY8EoodwWFGCVtkLHXUR+wA==", "dev": true, "engines": { "node": "^12.20.0 || ^14.15.0 || >=16.10.0", @@ -2504,7 +2417,7 @@ }, "peerDependencies": { "@angular/compiler-cli": "^13.0.0", - "typescript": ">=4.4.3 <4.6", + "typescript": ">=4.4.3 <4.7", "webpack": "^5.30.0" } }, @@ -2666,51 +2579,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", - "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", - "dev": true, - "dependencies": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@schematics/angular/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@schematics/angular/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@socket.io/base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", @@ -2806,9 +2674,9 @@ } }, "node_modules/@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", "dev": true }, "node_modules/@types/express": { @@ -8000,9 +7868,9 @@ "optional": true }, "node_modules/node-forge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", - "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", + "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==", "dev": true, "engines": { "node": ">= 6.13.0" @@ -11088,13 +10956,13 @@ } }, "node_modules/webpack": { - "version": "5.67.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.67.0.tgz", - "integrity": "sha512-LjFbfMh89xBDpUMgA1W9Ur6Rn/gnr2Cq1jjHFPo4v6a79/ypznSYbAyPgGhwsxBtMIaEmDD1oJoA7BEYw/Fbrw==", + "version": "5.70.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", + "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", "dev": true, "dependencies": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.50", + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", @@ -11102,7 +10970,7 @@ "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.3", + "enhanced-resolve": "^5.9.2", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -11564,12 +11432,12 @@ } }, "@angular-devkit/architect": { - "version": "0.1302.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1302.6.tgz", - "integrity": "sha512-NztzorUMfwJeRaT7SY00Y8WSqc2lQYuF11yNoyEm7Dae3V7VZ28rW2Z9RwibP27rYQL0RjSMaz2wKITHX2vOAw==", + "version": "0.1303.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.0.tgz", + "integrity": "sha512-kTcKB917ICA8j53SGo4gn+qAlzx8si+iHnOTbp5QlMr7qt/Iz07SVVI8mRlMD6c6lr7eE/fVlCLzEZ1+WCQpTA==", "dev": true, "requires": { - "@angular-devkit/core": "13.2.6", + "@angular-devkit/core": "13.3.0", "rxjs": "6.6.7" }, "dependencies": { @@ -11591,15 +11459,15 @@ } }, "@angular-devkit/build-angular": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.2.6.tgz", - "integrity": "sha512-Y2ojy6xbZ0kwScppcutLHBP8eW0qNOjburTISSBU/L5l/9FOeZ1E7yAreKuVu/qibZiLbSJfAhk+SLwhRHFSSQ==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.0.tgz", + "integrity": "sha512-3Ji7EeqGHj7i1Jgmeo3aIEXsnfKyFeQPpl65gcYmHwj5dP4lZzLSU4rMaWWUKksccgqCUXgPI2vKePTPazmikg==", "dev": true, "requires": { "@ampproject/remapping": "1.1.1", - "@angular-devkit/architect": "0.1302.6", - "@angular-devkit/build-webpack": "0.1302.6", - "@angular-devkit/core": "13.2.6", + "@angular-devkit/architect": "0.1303.0", + "@angular-devkit/build-webpack": "0.1303.0", + "@angular-devkit/core": "13.3.0", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -11610,7 +11478,7 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.2.6", + "@ngtools/webpack": "13.3.0", "ansi-colors": "4.1.1", "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", @@ -11656,7 +11524,7 @@ "text-table": "0.2.0", "tree-kill": "1.2.2", "tslib": "2.3.1", - "webpack": "5.67.0", + "webpack": "5.70.0", "webpack-dev-middleware": "5.3.0", "webpack-dev-server": "4.7.3", "webpack-merge": "5.8.0", @@ -11683,12 +11551,12 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.1302.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1302.6.tgz", - "integrity": "sha512-TYEh2n9tPe932rEIgdiSpojOqtDppW2jzb/empVqCkLF7WUZsXKvTanttZC34L6R2VD6SAGWhb6JDg75ghUVYA==", + "version": "0.1303.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.0.tgz", + "integrity": "sha512-a+Veg2oYn3RM2Kl148BReuONmD1kjbbYBnMUVi8nD6rvJPStFZkqN5s5ZkYybKeWnzMGaB3VasKR88z5XeH22A==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1302.6", + "@angular-devkit/architect": "0.1303.0", "rxjs": "6.6.7" }, "dependencies": { @@ -11710,9 +11578,9 @@ } }, "@angular-devkit/core": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.2.6.tgz", - "integrity": "sha512-8h2mWdBTN/dYwZuzKMg2IODlOWMdbJcpQG4XVrkk9ejCPP+3aX5Aa3glCe/voN6eBNiRfs8YDM0jxmpN2aWVtg==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", "dev": true, "requires": { "ajv": "8.9.0", @@ -11753,20 +11621,6 @@ "rxjs": "6.6.7" }, "dependencies": { - "@angular-devkit/core": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", - "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - } - }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -11834,47 +11688,6 @@ "semver": "7.3.5", "symbol-observable": "4.0.0", "uuid": "8.3.2" - }, - "dependencies": { - "@angular-devkit/architect": { - "version": "0.1303.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.0.tgz", - "integrity": "sha512-kTcKB917ICA8j53SGo4gn+qAlzx8si+iHnOTbp5QlMr7qt/Iz07SVVI8mRlMD6c6lr7eE/fVlCLzEZ1+WCQpTA==", - "dev": true, - "requires": { - "@angular-devkit/core": "13.3.0", - "rxjs": "6.6.7" - } - }, - "@angular-devkit/core": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", - "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - } - }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } } }, "@angular/common": { @@ -13270,9 +13083,9 @@ } }, "@ngtools/webpack": { - "version": "13.2.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.2.6.tgz", - "integrity": "sha512-N8SvRV91+/57TcAfbghc0k0tKCukw/7KqbDaLPAQTGFekJ4xMGT3elMzOyBXTH3Hvp5HL8/hiBt2tG04qiMf+w==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.0.tgz", + "integrity": "sha512-QbTQWXK2WzYU+aKKVDG0ya7WYT+6rNAUXVt5ov9Nz1SGgDeozpiOx8ZqPWUvnToTY8EoodwWFGCVtkLHXUR+wA==", "dev": true, "requires": {} }, @@ -13401,37 +13214,6 @@ "@angular-devkit/core": "13.3.0", "@angular-devkit/schematics": "13.3.0", "jsonc-parser": "3.0.0" - }, - "dependencies": { - "@angular-devkit/core": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", - "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - } - }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } } }, "@socket.io/base64-arraybuffer": { @@ -13523,9 +13305,9 @@ } }, "@types/estree": { - "version": "0.0.50", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", - "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==", + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", "dev": true }, "@types/express": { @@ -17408,9 +17190,9 @@ "optional": true }, "node-forge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", - "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", + "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==", "dev": true }, "node-gyp": { @@ -19677,13 +19459,13 @@ } }, "webpack": { - "version": "5.67.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.67.0.tgz", - "integrity": "sha512-LjFbfMh89xBDpUMgA1W9Ur6Rn/gnr2Cq1jjHFPo4v6a79/ypznSYbAyPgGhwsxBtMIaEmDD1oJoA7BEYw/Fbrw==", + "version": "5.70.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", + "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", "dev": true, "requires": { - "@types/eslint-scope": "^3.7.0", - "@types/estree": "^0.0.50", + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", @@ -19691,7 +19473,7 @@ "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.3", + "enhanced-resolve": "^5.9.2", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", diff --git a/frontend/package.json b/frontend/package.json index a0e8455..b3e0baf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,7 +26,7 @@ "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.2.5", + "@angular-devkit/build-angular": "~13.3.0", "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", From 9fa123489abf52ccfc7ebbc7048d5c6ad63146b3 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sat, 19 Mar 2022 20:25:34 +0100 Subject: [PATCH 028/263] Try to fix cors --- api/app.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/api/app.py b/api/app.py index 9a26db5..193bb4b 100644 --- a/api/app.py +++ b/api/app.py @@ -18,11 +18,7 @@ def create_app(): application = APIFlask(__name__, openapi_blueprint_url_prefix='/api') application.config.from_object("config.ConfigClass") - CORS(application, resource={ - r"/*": { - "origins": "*" - } - }) + CORS(application, resources={r"*": {"origins": "*"}}) application.app_context().push() From 1f31311b4e06101d0b2c243195e5b515aac70f88 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 21 Mar 2022 10:30:27 +0100 Subject: [PATCH 029/263] Set pool_recycle and pool_size to maybe fix database errors --- api/config.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/config.py b/api/config.py index 923f3b8..d3f69e6 100644 --- a/api/config.py +++ b/api/config.py @@ -21,6 +21,10 @@ class ConfigClass(object): (os.getenv("MYSQL_PORT") or str(3306)) + "/" + \ os.getenv('MYSQL_DATABASE') SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids SQLAlchemy warning + SQLALCHEMY_ENGINE_OPTIONS = { + 'pool_size': 100, + 'pool_recycle': 240 # 4 minutes + } # openapi/Swagger config SERVERS = [ From a9effa751d9b375131617e86173d78319c8c2c31 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 22 Mar 2022 08:14:04 +0100 Subject: [PATCH 030/263] Removed unnecessary requirements --- api/requirements.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/api/requirements.txt b/api/requirements.txt index 91de9c7..70218cf 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,12 +1,9 @@ Flask~=2.0.3 python-dotenv==0.19.2 -requests==2.27.1 uwsgi==2.0.20 Flask_SQLAlchemy==2.5.1 python-dotenv==0.19.2 pymysql==1.0.2 pyjwt==2.3.0 apiflask==0.12.0 -flask-swagger-ui==3.36.0 -flask-cors==3.0.10 - +flask-cors==3.0.10 \ No newline at end of file From 672f1cc2845c22dcb17ec5255f08875c50a18c66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Mar 2022 09:35:49 +0000 Subject: [PATCH 031/263] Bump @types/node from 17.0.21 to 17.0.22 in /frontend Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 17.0.21 to 17.0.22. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 14 +++++++------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d149dc2..1eb150b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,7 +28,7 @@ "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", - "@types/node": "^17.0.21", + "@types/node": "^17.0.22", "jasmine-core": "~4.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2730,9 +2730,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "17.0.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", - "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "version": "17.0.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.22.tgz", + "integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw==", "dev": true }, "node_modules/@types/parse-json": { @@ -13361,9 +13361,9 @@ "dev": true }, "@types/node": { - "version": "17.0.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz", - "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", + "version": "17.0.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.22.tgz", + "integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw==", "dev": true }, "@types/parse-json": { diff --git a/frontend/package.json b/frontend/package.json index 8b58ec3..21e5411 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,7 @@ "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", - "@types/node": "^17.0.21", + "@types/node": "^17.0.22", "jasmine-core": "~4.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", From 7f90f2c8127e83480dc59e04aa494b2c98607f41 Mon Sep 17 00:00:00 2001 From: Rripped <75929322+Rripped@users.noreply.github.com> Date: Tue, 22 Mar 2022 10:44:01 +0100 Subject: [PATCH 032/263] Update README.md --- documentation/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/documentation/README.md b/documentation/README.md index b575c73..2478c4d 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -1,5 +1,8 @@ # Dokumentation +##Swagger Documentation +Visit https://aktienbot.flokaiser.com/api/docs + ## API - `api/openapi.json` - OpenAPI-Dokumentation @@ -25,4 +28,4 @@ Function|Admin|No Admin| |Delete user|yes|only current user| |Update users|yes|only current user| |Set admin state|yes|no| -|Show all users|yes|no| \ No newline at end of file +|Show all users|yes|no| From f5f7ccd5a9c55bf5b2f835addefa845b29eb3b83 Mon Sep 17 00:00:00 2001 From: Rripped <75929322+Rripped@users.noreply.github.com> Date: Tue, 22 Mar 2022 10:44:28 +0100 Subject: [PATCH 033/263] Update README.md --- documentation/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/README.md b/documentation/README.md index 2478c4d..3bdec2f 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -1,6 +1,6 @@ # Dokumentation -##Swagger Documentation +## Swagger Documentation Visit https://aktienbot.flokaiser.com/api/docs ## API From bd5768527e6cec8966c451bb30159b018a5355be Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 22 Mar 2022 11:20:04 +0100 Subject: [PATCH 034/263] Improve api for bot --- api/auth.py | 3 +++ api/helper_functions.py | 31 +++++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/api/auth.py b/api/auth.py index 7db89a7..8096012 100644 --- a/api/auth.py +++ b/api/auth.py @@ -11,6 +11,9 @@ def verify_token(token): if token is None: return False + if ':' in token: # Bot token + token = token.split(":")[0] + try: jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"]) return True diff --git a/api/helper_functions.py b/api/helper_functions.py index 2791494..f5e6862 100644 --- a/api/helper_functions.py +++ b/api/helper_functions.py @@ -38,11 +38,30 @@ def extract_token_data(token): return None -def get_username_from_token_data(token_data): - if token_data is not None: - return token_data['username'] - else: - return None +def get_username_from_token_data(): + if 'Authorization' in request.headers: + token = request.headers['Authorization'].split(" ")[1] + + if token is not None: + if ':' in token: # Maybe bot token, check if token valid and return username after ":" then + username = token.split(":")[1] + token = token.split(":")[0] + + try: + if jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['username'] == "bot": + return username + else: + return None + except jwt.exceptions.DecodeError: + return None + + else: # "Normal" token, extract username from token + try: + return jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['username'] + except jwt.exceptions.DecodeError: + return None + + return None def get_user_id_from_username(username): @@ -54,7 +73,7 @@ def get_user_id_from_username(username): def get_username_or_abort_401(): # get username from jwt token - username = get_username_from_token_data(extract_token_data(get_token())) + username = get_username_from_token_data() if username is None: # If token not provided or invalid -> return 401 code abort(401, message="Unable to login") From 40d3e3238d16fb4ff5e1008ab0af78d29f9a1845 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 22 Mar 2022 11:21:39 +0100 Subject: [PATCH 035/263] Standardization of json responses --- api/api_blueprint_keyword.py | 11 +++++------ api/api_blueprint_portfolio.py | 5 ++--- api/api_blueprint_shares.py | 13 ++++++------- api/api_blueprint_transactions.py | 9 ++++----- api/api_blueprint_user.py | 20 ++++++++++---------- api/config.py | 2 +- api/helper_functions.py | 6 +++++- api/{scheme.py => schema.py} | 0 8 files changed, 33 insertions(+), 33 deletions(-) rename api/{scheme.py => schema.py} (100%) diff --git a/api/api_blueprint_keyword.py b/api/api_blueprint_keyword.py index 7474bcb..232a97b 100644 --- a/api/api_blueprint_keyword.py +++ b/api/api_blueprint_keyword.py @@ -1,12 +1,11 @@ import os from apiflask import APIBlueprint, abort -from flask import jsonify from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401 +from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response from auth import auth -from scheme import KeywordResponseSchema, KeywordSchema, DeleteSuccessfulSchema +from schema import KeywordResponseSchema, KeywordSchema, DeleteSuccessfulSchema from models import Keyword keyword_blueprint = APIBlueprint('keyword', __name__, url_prefix='/api') @@ -35,7 +34,7 @@ def add_keyword(data): db.session.add(new_keyword) db.session.commit() - return jsonify({"status": 200, "text": "Successfully added keyword", "data": new_keyword.as_dict()}) + return make_response(new_keyword.as_dict(), 200, "Successfully added keyword") else: abort(500, message="Keyword already exist for this user") @@ -55,7 +54,7 @@ def remove_keyword(data): db.session.query(Keyword).filter_by(keyword=key, user_id=get_user_id_from_username(username)).delete() db.session.commit() - return jsonify({"status": 200, "text": "Successfully removed keyword", "data": {}}) + return make_response({}, 200, "Successfully removed keyword") @keyword_blueprint.route('/keywords', methods=['GET']) @@ -72,7 +71,7 @@ def get_keywords(): for row in keywords: return_keywords.append(row.as_dict()) - return jsonify({"status": 200, "text": "Successfully loaded keywords", "data": return_keywords}) + return make_response(return_keywords, 200, "Successfully loaded keywords") def check_if_keyword_data_exists(data): diff --git a/api/api_blueprint_portfolio.py b/api/api_blueprint_portfolio.py index 2a54275..5970ced 100644 --- a/api/api_blueprint_portfolio.py +++ b/api/api_blueprint_portfolio.py @@ -1,10 +1,9 @@ import os from apiflask import APIBlueprint -from flask import jsonify from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401 +from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response from models import Transaction from auth import auth @@ -30,4 +29,4 @@ def get_portfolio(): else: return_portfolio[row.symbol] = {"count": row.count, "last_transaction": row.time} - return jsonify({"status": 200, "text": "Successfully loaded symbols", "data": return_portfolio}) + return make_response(return_portfolio, 200, "Successfully loaded symbols") diff --git a/api/api_blueprint_shares.py b/api/api_blueprint_shares.py index ef68d99..3e18e8c 100644 --- a/api/api_blueprint_shares.py +++ b/api/api_blueprint_shares.py @@ -1,13 +1,12 @@ import os from apiflask import APIBlueprint, abort -from flask import jsonify from auth import auth from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401 +from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response from models import Share -from scheme import SymbolSchema, SymbolResponseSchema, DeleteSuccessfulSchema +from schema import SymbolSchema, SymbolResponseSchema, DeleteSuccessfulSchema shares_blueprint = APIBlueprint('share', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -35,9 +34,9 @@ def add_symbol(data): db.session.add(new_symbol) db.session.commit() - return jsonify({"status": 200, "text": "Successfully added symbol", "data": new_symbol.as_dict()}) + return make_response(new_symbol.as_dict(), 200, "Successfully added symbol") else: - return jsonify({"status": 500, "text": "Symbol already exist for this user"}) + return make_response({}, 500, "Symbol already exist for this user") @shares_blueprint.route('/share', methods=['DELETE']) @@ -55,7 +54,7 @@ def remove_symbol(data): db.session.query(Share).filter_by(symbol=symbol, user_id=get_user_id_from_username(username)).delete() db.session.commit() - return jsonify({"status": 200, "text": "Successfully removed symbol", "data": {}}) + return make_response({}, 200, "Successfully removed symbol") @shares_blueprint.route('/shares', methods=['GET']) @@ -72,7 +71,7 @@ def get_symbol(): for row in symbols: return_symbols.append(row.as_dict()) - return jsonify({"status": 200, "text": "Successfully loaded symbols", "data": return_symbols}) + return make_response(return_symbols, 200, "Successfully loaded symbols") def check_if_symbol_data_exists(data): diff --git a/api/api_blueprint_transactions.py b/api/api_blueprint_transactions.py index 2a8fed2..b0e58fc 100644 --- a/api/api_blueprint_transactions.py +++ b/api/api_blueprint_transactions.py @@ -2,12 +2,11 @@ import os import datetime from apiflask import abort, APIBlueprint -from flask import jsonify from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401 +from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response from models import Transaction -from scheme import TransactionSchema +from schema import TransactionSchema from auth import auth transaction_blueprint = APIBlueprint('transaction', __name__, url_prefix='/api') @@ -34,7 +33,7 @@ def add_transaction(data): db.session.add(new_transaction) db.session.commit() - return jsonify({"status": 200, "text": "Successfully added transaction", "data": new_transaction.as_dict()}) + return make_response(new_transaction.as_dict(), 200, "Successfully added transaction") @transaction_blueprint.route('/transactions', methods=['GET']) @@ -51,7 +50,7 @@ def get_transaction(): for row in transactions: return_transactions.append(row.as_dict()) - return jsonify({"status": 200, "text": "Successfully loaded transactions", "data": return_transactions}) + return make_response(return_transactions, 200, "Successfully loaded transactions") def check_if_transaction_data_exists(data): diff --git a/api/api_blueprint_user.py b/api/api_blueprint_user.py index c7e6940..747a82f 100644 --- a/api/api_blueprint_user.py +++ b/api/api_blueprint_user.py @@ -3,12 +3,11 @@ import os import jwt from apiflask import APIBlueprint, abort -from flask import jsonify from db import db -from helper_functions import check_password, hash_password, get_username_or_abort_401, abort_if_no_admin +from helper_functions import check_password, hash_password, get_username_or_abort_401, abort_if_no_admin, make_response from models import User -from scheme import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema +from schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema from auth import auth users_blueprint = APIBlueprint('users', __name__, url_prefix='/api') @@ -26,7 +25,7 @@ def users(): for i in User.query.all(): res.append(i.as_dict()) - return jsonify({"status": 200, "data": res}) + return make_response(res, 200, "Successfully received all users") @users_blueprint.route('/user', methods=['GET']) @@ -38,7 +37,7 @@ def user(): res = db.session.query(User).filter_by(username=username).first().as_dict() - return jsonify({"status": 200, "data": res}) + return make_response(res, 200, "Successfully received current user data") @users_blueprint.route('/user/login', methods=['POST']) @@ -60,7 +59,8 @@ def login(data): abort(500, message="Unable to login") token = jwt.encode({'username': query_user.username, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=45)}, os.getenv('SECRET_KEY'), "HS256") - return jsonify({"status": 200, "text": "Successfully logged in", "data": {"token": token}}) + + return make_response({"token": token}, 200, "Successfully logged in") @users_blueprint.route('/user/register', methods=['POST']) @@ -86,7 +86,7 @@ def register(data): db.session.add(new_user) db.session.commit() - return jsonify({"status": 200, "text": "Successfully registered user", "data": new_user.as_dict()}) + return make_response(new_user.as_dict(), 200, "Successfully registered user") @users_blueprint.route('/user', methods=['PUT']) @@ -114,7 +114,7 @@ def update_user(data): db.session.commit() - return jsonify({"status": 200, "text": "Successfully updated user", "data": {}}) + return make_response({}, 200, "Successfully updated user") @users_blueprint.route('/user/setAdmin', methods=['PUT']) @@ -138,7 +138,7 @@ def set_admin(data): query_user.admin = admin db.session.commit() - return jsonify({"status": 200, "text": "Successfully updated users admin rights", "data": {}}) + return make_response({}, 200, "Successfully updated users admin rights") @users_blueprint.route('/user', methods=['DELETE']) @@ -160,7 +160,7 @@ def delete_user(data): db.session.query(User).filter_by(username=username).delete() db.session.commit() - return jsonify({"status": 200, "text": "Successfully removed user", "data": {}}) + return make_response({}, 200, "Successfully removed user") def check_if_user_data_exists(data): diff --git a/api/config.py b/api/config.py index d3f69e6..a12c60a 100644 --- a/api/config.py +++ b/api/config.py @@ -2,7 +2,7 @@ import os from dotenv import load_dotenv -from scheme import BaseResponseSchema +from schema import BaseResponseSchema load_dotenv() diff --git a/api/helper_functions.py b/api/helper_functions.py index f5e6862..59991c6 100644 --- a/api/helper_functions.py +++ b/api/helper_functions.py @@ -4,7 +4,7 @@ import uuid import jwt from apiflask import abort -from flask import request +from flask import request, jsonify from db import db from models import User @@ -90,3 +90,7 @@ def is_user_admin(): username = get_username_or_abort_401() return db.session.query(User).filter_by(username=username).first().admin + + +def make_response(data, status=200, text=""): + return jsonify({"status": status, "text": text, "data": {"token": data}}) diff --git a/api/scheme.py b/api/schema.py similarity index 100% rename from api/scheme.py rename to api/schema.py From 769c8006caf0b20f324754bb0e1601d3c7fe8f86 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 22 Mar 2022 11:21:58 +0100 Subject: [PATCH 036/263] Create database before request --- api/app.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/app.py b/api/app.py index 193bb4b..933b471 100644 --- a/api/app.py +++ b/api/app.py @@ -24,9 +24,6 @@ def create_app(): db.init_app(application) - # Create all tables - db.create_all() - # api blueprints application.register_blueprint(keyword_blueprint) application.register_blueprint(shares_blueprint) @@ -34,6 +31,10 @@ def create_app(): application.register_blueprint(portfolio_blueprint) application.register_blueprint(users_blueprint) + @application.before_first_request + def init_database(): + db.create_all() + return application From af1751657db27750780a23e078f21ea86b44f1e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Mar 2022 09:39:03 +0000 Subject: [PATCH 037/263] Bump @types/node from 17.0.22 to 17.0.23 in /frontend Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 17.0.22 to 17.0.23. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 14 +++++++------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1eb150b..7999205 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,7 +28,7 @@ "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", - "@types/node": "^17.0.22", + "@types/node": "^17.0.23", "jasmine-core": "~4.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", @@ -2730,9 +2730,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "17.0.22", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.22.tgz", - "integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw==", + "version": "17.0.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", + "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==", "dev": true }, "node_modules/@types/parse-json": { @@ -13361,9 +13361,9 @@ "dev": true }, "@types/node": { - "version": "17.0.22", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.22.tgz", - "integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw==", + "version": "17.0.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", + "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==", "dev": true }, "@types/parse-json": { diff --git a/frontend/package.json b/frontend/package.json index 21e5411..ba1bbcb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,7 @@ "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", - "@types/node": "^17.0.22", + "@types/node": "^17.0.23", "jasmine-core": "~4.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", From 8aa5ea9e6beb36995b5d039a1975397535607765 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Mar 2022 09:39:13 +0000 Subject: [PATCH 038/263] Bump @angular/material from 13.3.0 to 13.3.1 in /frontend Bumps [@angular/material](https://github.com/angular/components) from 13.3.0 to 13.3.1. - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/13.3.0...13.3.1) --- updated-dependencies: - dependency-name: "@angular/material" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 28 ++++++++++++++-------------- frontend/package.json | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1eb150b..7134c78 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,7 +14,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.3.0", + "@angular/material": "^13.3.1", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", @@ -345,9 +345,9 @@ } }, "node_modules/@angular/cdk": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.0.tgz", - "integrity": "sha512-TyTcs9/Vd0TyQIjRf40xCDrTBK2GGCUrrHCVuHTXIqL8yvaGRnz815fL9X3hSZySCcbRV4NeK1yWeiZ9jf4Vxw==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.1.tgz", + "integrity": "sha512-JUCvhN6XomY5JOMH7Qyj/JodAuAtKgLlJYKs0tPmjCkGcBdR/T3/hOJ9gSRhz8PCOoiEORj+3xCh547oJRyesA==", "dependencies": { "tslib": "^2.3.0" }, @@ -577,15 +577,15 @@ } }, "node_modules/@angular/material": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.0.tgz", - "integrity": "sha512-g9WRuzUo0HPeEZKQi3uRZU3lfFwcdVqdXL8F9vxpR6fg0u9jNF+Wm+sIlsADS93h/zS0IZXYecge0NU8T7Kd0g==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.1.tgz", + "integrity": "sha512-c9BYNxvZvxuCA5QticqfEhn3nuspUnwxIVejjO5ZTqKlkK+vQGKN3QhshHLrMHeFYyjao/E8jVnJX7ahitPM9w==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "^13.0.0 || ^14.0.0-0", - "@angular/cdk": "13.3.0", + "@angular/cdk": "13.3.1", "@angular/common": "^13.0.0 || ^14.0.0-0", "@angular/core": "^13.0.0 || ^14.0.0-0", "@angular/forms": "^13.0.0 || ^14.0.0-0", @@ -11647,9 +11647,9 @@ } }, "@angular/cdk": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.0.tgz", - "integrity": "sha512-TyTcs9/Vd0TyQIjRf40xCDrTBK2GGCUrrHCVuHTXIqL8yvaGRnz815fL9X3hSZySCcbRV4NeK1yWeiZ9jf4Vxw==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.1.tgz", + "integrity": "sha512-JUCvhN6XomY5JOMH7Qyj/JodAuAtKgLlJYKs0tPmjCkGcBdR/T3/hOJ9gSRhz8PCOoiEORj+3xCh547oJRyesA==", "requires": { "parse5": "^5.0.0", "tslib": "^2.3.0" @@ -11809,9 +11809,9 @@ } }, "@angular/material": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.0.tgz", - "integrity": "sha512-g9WRuzUo0HPeEZKQi3uRZU3lfFwcdVqdXL8F9vxpR6fg0u9jNF+Wm+sIlsADS93h/zS0IZXYecge0NU8T7Kd0g==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.1.tgz", + "integrity": "sha512-c9BYNxvZvxuCA5QticqfEhn3nuspUnwxIVejjO5ZTqKlkK+vQGKN3QhshHLrMHeFYyjao/E8jVnJX7ahitPM9w==", "requires": { "tslib": "^2.3.0" } diff --git a/frontend/package.json b/frontend/package.json index 21e5411..1c09893 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.3.0", + "@angular/material": "^13.3.1", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", From 047d4a7dc21f982b2f6122ee86933650d7a6a955 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Mar 2022 08:07:18 +0000 Subject: [PATCH 039/263] Bump minimist from 1.2.5 to 1.2.6 in /frontend Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6. - [Release notes](https://github.com/substack/minimist/releases) - [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6) --- updated-dependencies: - dependency-name: minimist dependency-type: indirect ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1eb150b..1d6746b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7642,9 +7642,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/minipass": { @@ -17013,9 +17013,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "minipass": { From ef0b6800159e1c176f4d8bf21a680553b96385fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Mar 2022 09:41:06 +0000 Subject: [PATCH 040/263] Bump python-dotenv from 0.19.2 to 0.20.0 in /api Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 0.19.2 to 0.20.0. - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/theskumar/python-dotenv/compare/v0.19.2...v0.20.0) --- updated-dependencies: - dependency-name: python-dotenv dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- api/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/requirements.txt b/api/requirements.txt index 70218cf..bcc5fee 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,8 +1,8 @@ Flask~=2.0.3 -python-dotenv==0.19.2 +python-dotenv==0.20.0 uwsgi==2.0.20 Flask_SQLAlchemy==2.5.1 -python-dotenv==0.19.2 +python-dotenv==0.20.0 pymysql==1.0.2 pyjwt==2.3.0 apiflask==0.12.0 From d41c8876568bdfb76745e2b190eacf3559b027db Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:03:29 +0100 Subject: [PATCH 041/263] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 1dbcb5d..d4b31d9 100644 --- a/README.md +++ b/README.md @@ -15,5 +15,4 @@ WebEngineering2 Projekt: Aktien und News Bot für Telegram - Update auf Anfrage ## Dokumentation -- Postman-API -> docs/postman.json -- Datenbank -> database/* +-> README.md in /documentation From 49acdcaad0e08468cea75aa8ee67601055df3a36 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:10:14 +0100 Subject: [PATCH 042/263] Create pipeline.yml --- .woodpecker/pipeline.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .woodpecker/pipeline.yml diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml new file mode 100644 index 0000000..8eb6d4b --- /dev/null +++ b/.woodpecker/pipeline.yml @@ -0,0 +1,5 @@ +pipeline: + backend: + image: golang + commands: + - echo "Test123" From 5d67cd12730384cfc74e30902a630ff2175e9858 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:16:14 +0100 Subject: [PATCH 043/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 66 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 8eb6d4b..5f6e698 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -1,5 +1,67 @@ pipeline: - backend: + generate_tag: image: golang commands: - - echo "Test123" + - echo -n "${DRONE_BRANCH//\//-}-${DRONE_COMMIT_SHA:0:8}, latest" > .tags + + publish_api: + image: plugins/docker + settings: + username: + from_secret: username + password: + from_secret: password + registry: + from_secret: registry + repo: + from_secret: repo_api + dockerfile: api/Dockerfile + + publish_frontend: + image: plugins/docker + settings: + username: + from_secret: username + password: + from_secret: password + registry: + from_secret: registry + repo: + from_secret: repo_frontend + dockerfile: frontend/Dockerfile + + publish_bot: + image: plugins/docker + settings: + username: + from_secret: username + password: + from_secret: password + registry: + from_secret: registry + repo: + from_secret: repo_bot + dockerfile: telegram_bot/Dockerfile + + deploy: + image: appleboy/drone-ssh + network_mode: host + environment: + REPO: + from_secret: repo + IP: + from_secret: deploy_ip + NET: + from_secret: deploy_net + NAME: + from_secret: deploy_name + PLUGIN_HOST: + from_secret: ssh_host + PLUGIN_USERNAME: + from_secret: ssh_user + PLUGIN_PASSWORD: + from_secret: ssh_password + PLUGIN_SCRIPT: /opt/docker/TelegramAktienBot/deploy.sh + +branches: + include: [ main ] From 9303943199ccdc8fc13fdfef559f7245417515ae Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:18:52 +0100 Subject: [PATCH 044/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 5f6e698..e2df389 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -63,5 +63,4 @@ pipeline: from_secret: ssh_password PLUGIN_SCRIPT: /opt/docker/TelegramAktienBot/deploy.sh -branches: - include: [ main ] +branches: main From 433b6cc50dbf9cb68087644bfc2aa8a6ed1e88e7 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:19:13 +0100 Subject: [PATCH 045/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 61 ---------------------------------------- 1 file changed, 61 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index e2df389..b2df43f 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -3,64 +3,3 @@ pipeline: image: golang commands: - echo -n "${DRONE_BRANCH//\//-}-${DRONE_COMMIT_SHA:0:8}, latest" > .tags - - publish_api: - image: plugins/docker - settings: - username: - from_secret: username - password: - from_secret: password - registry: - from_secret: registry - repo: - from_secret: repo_api - dockerfile: api/Dockerfile - - publish_frontend: - image: plugins/docker - settings: - username: - from_secret: username - password: - from_secret: password - registry: - from_secret: registry - repo: - from_secret: repo_frontend - dockerfile: frontend/Dockerfile - - publish_bot: - image: plugins/docker - settings: - username: - from_secret: username - password: - from_secret: password - registry: - from_secret: registry - repo: - from_secret: repo_bot - dockerfile: telegram_bot/Dockerfile - - deploy: - image: appleboy/drone-ssh - network_mode: host - environment: - REPO: - from_secret: repo - IP: - from_secret: deploy_ip - NET: - from_secret: deploy_net - NAME: - from_secret: deploy_name - PLUGIN_HOST: - from_secret: ssh_host - PLUGIN_USERNAME: - from_secret: ssh_user - PLUGIN_PASSWORD: - from_secret: ssh_password - PLUGIN_SCRIPT: /opt/docker/TelegramAktienBot/deploy.sh - -branches: main From c9785d86ac972e4e1167c7955738d69f97f9d5c7 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:20:35 +0100 Subject: [PATCH 046/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 61 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index b2df43f..3d04b7e 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -3,3 +3,64 @@ pipeline: image: golang commands: - echo -n "${DRONE_BRANCH//\//-}-${DRONE_COMMIT_SHA:0:8}, latest" > .tags + + publish_api: + image: plugins/docker + settings: + username: + from_secret: username + password: + from_secret: password + registry: + from_secret: registry + repo: + from_secret: repo_api + dockerfile: api/Dockerfile + + publish_frontend: + image: plugins/docker + settings: + username: + from_secret: username + password: + from_secret: password + registry: + from_secret: registry + repo: + from_secret: repo_frontend + dockerfile: frontend/Dockerfile + + publish_bot: + image: plugins/docker + settings: + username: + from_secret: username + password: + from_secret: password + registry: + from_secret: registry + repo: + from_secret: repo_bot + dockerfile: telegram_bot/Dockerfile + + deploy: + image: appleboy/drone-ssh + network_mode: host + environment: + REPO: + from_secret: repo + IP: + from_secret: deploy_ip + NET: + from_secret: deploy_net + NAME: + from_secret: deploy_name + PLUGIN_HOST: + from_secret: ssh_host + PLUGIN_USERNAME: + from_secret: ssh_user + PLUGIN_PASSWORD: + from_secret: ssh_password + PLUGIN_SCRIPT: /opt/docker/TelegramAktienBot/deploy.sh + +branches: main From 5acd9ee60bc3ab8ae7c65d02c2c5a8c99d6927b7 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:25:16 +0100 Subject: [PATCH 047/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 3d04b7e..6f6a4d3 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -46,21 +46,22 @@ pipeline: deploy: image: appleboy/drone-ssh network_mode: host + secrets: + - repo + target: REPO + - deploy_ip + target: IP + - deploy_net + target: NET + - deploy_name + target: NAME + - ssh_host + target: PLUGIN_HOST + - ssh_user + target: PLUGIN_USERNAME + - ssh_password + target: PLUGIN_PASSWORD environment: - REPO: - from_secret: repo - IP: - from_secret: deploy_ip - NET: - from_secret: deploy_net - NAME: - from_secret: deploy_name - PLUGIN_HOST: - from_secret: ssh_host - PLUGIN_USERNAME: - from_secret: ssh_user - PLUGIN_PASSWORD: - from_secret: ssh_password - PLUGIN_SCRIPT: /opt/docker/TelegramAktienBot/deploy.sh + PLUGIN_SCRIPT=/opt/docker/TelegramAktienBot/deploy.sh branches: main From d5d5796c9b71385f07013da460cac3c9e51858a9 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:26:10 +0100 Subject: [PATCH 048/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 90 ++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 6f6a4d3..057e1d8 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -17,51 +17,51 @@ pipeline: from_secret: repo_api dockerfile: api/Dockerfile - publish_frontend: - image: plugins/docker - settings: - username: - from_secret: username - password: - from_secret: password - registry: - from_secret: registry - repo: - from_secret: repo_frontend - dockerfile: frontend/Dockerfile +# publish_frontend: +# image: plugins/docker +# settings: +# username: +# from_secret: username +# password: +# from_secret: password +# registry: +# from_secret: registry +# repo: +# from_secret: repo_frontend +# dockerfile: frontend/Dockerfile - publish_bot: - image: plugins/docker - settings: - username: - from_secret: username - password: - from_secret: password - registry: - from_secret: registry - repo: - from_secret: repo_bot - dockerfile: telegram_bot/Dockerfile +# publish_bot: +# image: plugins/docker +# settings: +# username: +# from_secret: username +# password: +# from_secret: password +# registry: +# from_secret: registry +# repo: +# from_secret: repo_bot +# dockerfile: telegram_bot/Dockerfile - deploy: - image: appleboy/drone-ssh - network_mode: host - secrets: - - repo - target: REPO - - deploy_ip - target: IP - - deploy_net - target: NET - - deploy_name - target: NAME - - ssh_host - target: PLUGIN_HOST - - ssh_user - target: PLUGIN_USERNAME - - ssh_password - target: PLUGIN_PASSWORD - environment: - PLUGIN_SCRIPT=/opt/docker/TelegramAktienBot/deploy.sh +# deploy: +# image: appleboy/drone-ssh +# network_mode: host +# secrets: +# - repo +# target: REPO +# - deploy_ip +# target: IP +# - deploy_net +# target: NET +# - deploy_name +# target: NAME +# - ssh_host +# target: PLUGIN_HOST +# - ssh_user +# target: PLUGIN_USERNAME +# - ssh_password +# target: PLUGIN_PASSWORD +# environment: +# PLUGIN_SCRIPT=/opt/docker/TelegramAktienBot/deploy.sh -branches: main +# branches: main From 8209ae91667dc8e16a82dfa5bac94d0a80574e77 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:31:59 +0100 Subject: [PATCH 049/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 057e1d8..e4cabb2 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -3,6 +3,7 @@ pipeline: image: golang commands: - echo -n "${DRONE_BRANCH//\//-}-${DRONE_COMMIT_SHA:0:8}, latest" > .tags + - ls publish_api: image: plugins/docker From 1ee0d8fdf8c5e18fcf40fd1a9a3ff48bed70653b Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:32:54 +0100 Subject: [PATCH 050/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index e4cabb2..3e63d5f 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -3,7 +3,7 @@ pipeline: image: golang commands: - echo -n "${DRONE_BRANCH//\//-}-${DRONE_COMMIT_SHA:0:8}, latest" > .tags - - ls + - ls api/ publish_api: image: plugins/docker From 736f895542a65263efc7bd27029f8506cf751202 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:37:56 +0100 Subject: [PATCH 051/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 3e63d5f..a5ad07f 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -1,22 +1,22 @@ pipeline: - generate_tag: - image: golang - commands: - - echo -n "${DRONE_BRANCH//\//-}-${DRONE_COMMIT_SHA:0:8}, latest" > .tags - - ls api/ +# generate_tag: +# image: golang +# commands: +# - echo -n "${DRONE_BRANCH//\//-}-${DRONE_COMMIT_SHA:0:8}, latest" > .tags +# - ls api/ publish_api: image: plugins/docker settings: - username: + - username: from_secret: username - password: + - password: from_secret: password - registry: + - registry: from_secret: registry - repo: + - repo: from_secret: repo_api - dockerfile: api/Dockerfile + - dockerfile: api/Dockerfile # publish_frontend: # image: plugins/docker From e3bb35fde7f041bc61e997e9438fb0d56d37aeba Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:43:18 +0100 Subject: [PATCH 052/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index a5ad07f..128c02a 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -5,18 +5,14 @@ pipeline: # - echo -n "${DRONE_BRANCH//\//-}-${DRONE_COMMIT_SHA:0:8}, latest" > .tags # - ls api/ + publish_api: - image: plugins/docker + image: woodpeckerci/plugin-docker-buildx settings: - - username: - from_secret: username - - password: - from_secret: password - - registry: - from_secret: registry - - repo: + repo: from_secret: repo_api - - dockerfile: api/Dockerfile + dockerfile: api/Dockerfile + platforms: linux/amd64 # publish_frontend: # image: plugins/docker From 69be6a2288604ef696c441589849016ec1f60386 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:44:05 +0100 Subject: [PATCH 053/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 128c02a..b00d43a 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -1,10 +1,8 @@ pipeline: -# generate_tag: -# image: golang -# commands: -# - echo -n "${DRONE_BRANCH//\//-}-${DRONE_COMMIT_SHA:0:8}, latest" > .tags -# - ls api/ - + generate_tag: + image: golang + commands: + - echo -n "${DRONE_BRANCH//\//-}-${DRONE_COMMIT_SHA:0:8}, latest" > .tags publish_api: image: woodpeckerci/plugin-docker-buildx From 173ef5e577a0c4152942c7b165d4b4807dab5f42 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:45:14 +0100 Subject: [PATCH 054/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index b00d43a..7e6ef71 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -2,7 +2,7 @@ pipeline: generate_tag: image: golang commands: - - echo -n "${DRONE_BRANCH//\//-}-${DRONE_COMMIT_SHA:0:8}, latest" > .tags + - echo -n "${CI_COMMIT_BRANCH//\//-}-${CI_COMMIT_SHA:0:8}, latest" > .tags publish_api: image: woodpeckerci/plugin-docker-buildx From e0c39baf349c51ac663fd20e48a957dab25be5aa Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 11:47:07 +0100 Subject: [PATCH 055/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 7e6ef71..b735914 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -7,8 +7,7 @@ pipeline: publish_api: image: woodpeckerci/plugin-docker-buildx settings: - repo: - from_secret: repo_api + repo: registry.flokaiser.com/aktienbot/api dockerfile: api/Dockerfile platforms: linux/amd64 From 4dc9c873794701fa1b119f98e9b919f0f97b0aaa Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:00:43 +0100 Subject: [PATCH 056/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index b735914..7e6ef71 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -7,7 +7,8 @@ pipeline: publish_api: image: woodpeckerci/plugin-docker-buildx settings: - repo: registry.flokaiser.com/aktienbot/api + repo: + from_secret: repo_api dockerfile: api/Dockerfile platforms: linux/amd64 From 31f8ec5d2257988bfb3accce1edcd1b3fac795c8 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:02:20 +0100 Subject: [PATCH 057/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 7e6ef71..fbe7bf1 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -9,6 +9,12 @@ pipeline: settings: repo: from_secret: repo_api + username: + from_secret: username + password: + from_secret: password + registry: + from_secret: registry dockerfile: api/Dockerfile platforms: linux/amd64 From 1855c9966c65b205935e5c5ddabc6527f7793688 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:05:30 +0100 Subject: [PATCH 058/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 56 +++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index fbe7bf1..4c1c6b5 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -4,7 +4,7 @@ pipeline: commands: - echo -n "${CI_COMMIT_BRANCH//\//-}-${CI_COMMIT_SHA:0:8}, latest" > .tags - publish_api: + build_api: image: woodpeckerci/plugin-docker-buildx settings: repo: @@ -18,32 +18,34 @@ pipeline: dockerfile: api/Dockerfile platforms: linux/amd64 -# publish_frontend: -# image: plugins/docker -# settings: -# username: -# from_secret: username -# password: -# from_secret: password -# registry: -# from_secret: registry -# repo: -# from_secret: repo_frontend -# dockerfile: frontend/Dockerfile - -# publish_bot: -# image: plugins/docker -# settings: -# username: -# from_secret: username -# password: -# from_secret: password -# registry: -# from_secret: registry -# repo: -# from_secret: repo_bot -# dockerfile: telegram_bot/Dockerfile - + build_bot: + image: woodpeckerci/plugin-docker-buildx + settings: + repo: + from_secret: repo_bot + username: + from_secret: username + password: + from_secret: password + registry: + from_secret: registry + dockerfile: telegram_bot/Dockerfile + platforms: linux/amd64 + + build_frontend: + image: woodpeckerci/plugin-docker-buildx + settings: + repo: + from_secret: repo_frontend + username: + from_secret: username + password: + from_secret: password + registry: + from_secret: registry + dockerfile: frontend/Dockerfile + platforms: linux/amd64 + # deploy: # image: appleboy/drone-ssh # network_mode: host From c5c9a70df981f346745b06c8947360485b643d98 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:09:57 +0100 Subject: [PATCH 059/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 41 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 4c1c6b5..611b424 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -46,25 +46,24 @@ pipeline: dockerfile: frontend/Dockerfile platforms: linux/amd64 -# deploy: -# image: appleboy/drone-ssh -# network_mode: host -# secrets: -# - repo -# target: REPO -# - deploy_ip -# target: IP -# - deploy_net -# target: NET -# - deploy_name -# target: NAME -# - ssh_host -# target: PLUGIN_HOST -# - ssh_user -# target: PLUGIN_USERNAME -# - ssh_password -# target: PLUGIN_PASSWORD -# environment: -# PLUGIN_SCRIPT=/opt/docker/TelegramAktienBot/deploy.sh + deploy: + image: appleboy/drone-ssh + network_mode: host + settings: + REPO: + from_secret: repo + IP: + from_secret: deploy_ip + NET: + from_secret: deploy_net + NAME: + from_secret: deploy_name + PLUGIN_HOST: + from_secret: ssh_host + PLUGIN_USERNAME: + from_secret: ssh_user + PLUGIN_PASSWORD: + from_secret: ssh_password + PLUGIN_SCRIPT: /opt/docker/TelegramAktienBot/deploy.sh -# branches: main +branches: main From f97354cfd1545f3afb5fcd6bced5efc7a500da60 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:16:55 +0100 Subject: [PATCH 060/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 86 ++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 611b424..83ce309 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -1,50 +1,50 @@ pipeline: - generate_tag: - image: golang - commands: - - echo -n "${CI_COMMIT_BRANCH//\//-}-${CI_COMMIT_SHA:0:8}, latest" > .tags +# generate_tag: +# image: golang +# commands: +# - echo -n "${CI_COMMIT_BRANCH//\//-}-${CI_COMMIT_SHA:0:8}, latest" > .tags - build_api: - image: woodpeckerci/plugin-docker-buildx - settings: - repo: - from_secret: repo_api - username: - from_secret: username - password: - from_secret: password - registry: - from_secret: registry - dockerfile: api/Dockerfile - platforms: linux/amd64 +# build_api: +# image: woodpeckerci/plugin-docker-buildx +# settings: +# repo: +# from_secret: repo_api +# username: +# from_secret: username +# password: +# from_secret: password +# registry: +# from_secret: registry +# dockerfile: api/Dockerfile +# platforms: linux/amd64 - build_bot: - image: woodpeckerci/plugin-docker-buildx - settings: - repo: - from_secret: repo_bot - username: - from_secret: username - password: - from_secret: password - registry: - from_secret: registry - dockerfile: telegram_bot/Dockerfile - platforms: linux/amd64 +# build_bot: +# image: woodpeckerci/plugin-docker-buildx +# settings: +# repo: +# from_secret: repo_bot +# username: +# from_secret: username +# password: +# from_secret: password +# registry: +# from_secret: registry +# dockerfile: telegram_bot/Dockerfile +# platforms: linux/amd64 - build_frontend: - image: woodpeckerci/plugin-docker-buildx - settings: - repo: - from_secret: repo_frontend - username: - from_secret: username - password: - from_secret: password - registry: - from_secret: registry - dockerfile: frontend/Dockerfile - platforms: linux/amd64 +# build_frontend: +# image: woodpeckerci/plugin-docker-buildx +# settings: +# repo: +# from_secret: repo_frontend +# username: +# from_secret: username +# password: +# from_secret: password +# registry: +# from_secret: registry +# dockerfile: frontend/Dockerfile +# platforms: linux/amd64 deploy: image: appleboy/drone-ssh From 9883ad6799b259d7fc46cdc9829b964a31c019db Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:17:34 +0100 Subject: [PATCH 061/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 83ce309..ba86a71 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -58,7 +58,7 @@ pipeline: from_secret: deploy_net NAME: from_secret: deploy_name - PLUGIN_HOST: + plugin_host: from_secret: ssh_host PLUGIN_USERNAME: from_secret: ssh_user From 74b7a18ec45fb54771c5a390c6b485909f8c2764 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:18:40 +0100 Subject: [PATCH 062/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index ba86a71..4f32859 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -58,12 +58,12 @@ pipeline: from_secret: deploy_net NAME: from_secret: deploy_name - plugin_host: + host: from_secret: ssh_host - PLUGIN_USERNAME: + username: from_secret: ssh_user - PLUGIN_PASSWORD: + password: from_secret: ssh_password - PLUGIN_SCRIPT: /opt/docker/TelegramAktienBot/deploy.sh + script: /opt/docker/TelegramAktienBot/deploy.sh branches: main From edebed53baa4626529a1a8af3aa43037f177c470 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:20:56 +0100 Subject: [PATCH 063/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 4f32859..335373f 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -49,21 +49,15 @@ pipeline: deploy: image: appleboy/drone-ssh network_mode: host - settings: - REPO: - from_secret: repo - IP: - from_secret: deploy_ip - NET: - from_secret: deploy_net - NAME: - from_secret: deploy_name + settings: host: from_secret: ssh_host username: from_secret: ssh_user password: from_secret: ssh_password - script: /opt/docker/TelegramAktienBot/deploy.sh + script: + - /opt/docker/TelegramAktienBot/deploy.sh + - ls branches: main From 84c945b3d8cf4845b7123256d85c718ca3e0637b Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:21:26 +0100 Subject: [PATCH 064/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 87 ++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 335373f..84edc74 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -1,50 +1,50 @@ pipeline: -# generate_tag: -# image: golang -# commands: -# - echo -n "${CI_COMMIT_BRANCH//\//-}-${CI_COMMIT_SHA:0:8}, latest" > .tags + generate_tag: + image: golang + commands: + - echo -n "${CI_COMMIT_BRANCH//\//-}-${CI_COMMIT_SHA:0:8}, latest" > .tags -# build_api: -# image: woodpeckerci/plugin-docker-buildx -# settings: -# repo: -# from_secret: repo_api -# username: -# from_secret: username -# password: -# from_secret: password -# registry: -# from_secret: registry -# dockerfile: api/Dockerfile -# platforms: linux/amd64 + build_api: + image: woodpeckerci/plugin-docker-buildx + settings: + repo: + from_secret: repo_api + username: + from_secret: username + password: + from_secret: password + registry: + from_secret: registry + dockerfile: api/Dockerfile + platforms: linux/amd64 -# build_bot: -# image: woodpeckerci/plugin-docker-buildx -# settings: -# repo: -# from_secret: repo_bot -# username: -# from_secret: username -# password: -# from_secret: password -# registry: -# from_secret: registry -# dockerfile: telegram_bot/Dockerfile -# platforms: linux/amd64 + build_bot: + image: woodpeckerci/plugin-docker-buildx + settings: + repo: + from_secret: repo_bot + username: + from_secret: username + password: + from_secret: password + registry: + from_secret: registry + dockerfile: telegram_bot/Dockerfile + platforms: linux/amd64 -# build_frontend: -# image: woodpeckerci/plugin-docker-buildx -# settings: -# repo: -# from_secret: repo_frontend -# username: -# from_secret: username -# password: -# from_secret: password -# registry: -# from_secret: registry -# dockerfile: frontend/Dockerfile -# platforms: linux/amd64 + build_frontend: + image: woodpeckerci/plugin-docker-buildx + settings: + repo: + from_secret: repo_frontend + username: + from_secret: username + password: + from_secret: password + registry: + from_secret: registry + dockerfile: frontend/Dockerfile + platforms: linux/amd64 deploy: image: appleboy/drone-ssh @@ -58,6 +58,5 @@ pipeline: from_secret: ssh_password script: - /opt/docker/TelegramAktienBot/deploy.sh - - ls branches: main From f50ab0fe7b212350ab8bec54270017453a328cb3 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:22:36 +0100 Subject: [PATCH 065/263] Delete .drone.yml --- .drone.yml | 77 ------------------------------------------------------ 1 file changed, 77 deletions(-) delete mode 100644 .drone.yml diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 62eaf5b..0000000 --- a/.drone.yml +++ /dev/null @@ -1,77 +0,0 @@ -kind: pipeline -type: docker -name: linux-amd64 - -platform: - arch: amd64 - os: linux - -steps: -- name: generate_tag - image: golang - commands: - - echo -n "${DRONE_BRANCH//\//-}-${DRONE_COMMIT_SHA:0:8}, latest" > .tags - - -- name: publish_api - image: plugins/docker - settings: - username: - from_secret: username - password: - from_secret: password - registry: - from_secret: registry - repo: - from_secret: repo_api - dockerfile: api/Dockerfile - -- name: publish_frontend - image: plugins/docker - settings: - username: - from_secret: username - password: - from_secret: password - registry: - from_secret: registry - repo: - from_secret: repo_frontend - dockerfile: frontend/Dockerfile - -- name: publish_bot - image: plugins/docker - settings: - username: - from_secret: username - password: - from_secret: password - registry: - from_secret: registry - repo: - from_secret: repo_bot - dockerfile: telegram_bot/Dockerfile - -- name: deploy - image: appleboy/drone-ssh - network_mode: host - environment: - REPO: - from_secret: repo - IP: - from_secret: deploy_ip - NET: - from_secret: deploy_net - NAME: - from_secret: deploy_name - PLUGIN_HOST: - from_secret: ssh_host - PLUGIN_USERNAME: - from_secret: ssh_user - PLUGIN_PASSWORD: - from_secret: ssh_password - PLUGIN_SCRIPT: /opt/docker/TelegramAktienBot/deploy.sh - -trigger: - branch: - - main From 6e7cf5df90139e60caa767d0bb6fe0347eb59c84 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:31:19 +0100 Subject: [PATCH 066/263] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4b31d9..022b2bc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://drone.flokaiser.com/api/badges/H4CK3R-01/TelegramAktienBot/status.svg?ref=main)](https://drone.flokaiser.com/H4CK3R-01/TelegramAktienBot) +[![Build Status](https://woodpecker.flokaiser.com/api/badges/WebEngineering2/TelegramAktienBot/status.svg)](https://woodpecker.flokaiser.com/WebEngineering2/TelegramAktienBot/) # TelegramAktienBot WebEngineering2 Projekt: Aktien und News Bot für Telegram From b3f76ec8bf7edf55857b3e7597d601f3a7f5a71b Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 10:08:43 +0200 Subject: [PATCH 067/263] Improved pipeline.yml --- .woodpecker/pipeline.yml | 44 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 84edc74..e2406ae 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -17,6 +17,23 @@ pipeline: from_secret: registry dockerfile: api/Dockerfile platforms: linux/amd64 + when: + path: "api/*" + + deploy_api: + image: appleboy/drone-ssh + network_mode: host + settings: + host: + from_secret: ssh_host + username: + from_secret: ssh_user + password: + from_secret: ssh_password + script: + - /opt/docker/TelegramAktienBot/deploy_api.sh + when: + path: "api/*" build_bot: image: woodpeckerci/plugin-docker-buildx @@ -31,7 +48,24 @@ pipeline: from_secret: registry dockerfile: telegram_bot/Dockerfile platforms: linux/amd64 - + when: + path: "telegram_bot/*" + + deploy_bot: + image: appleboy/drone-ssh + network_mode: host + settings: + host: + from_secret: ssh_host + username: + from_secret: ssh_user + password: + from_secret: ssh_password + script: + - /opt/docker/TelegramAktienBot/deploy_bot.sh + when: + path: "telegram_bot/*" + build_frontend: image: woodpeckerci/plugin-docker-buildx settings: @@ -45,8 +79,10 @@ pipeline: from_secret: registry dockerfile: frontend/Dockerfile platforms: linux/amd64 + when: + path: "frontend/*" - deploy: + deploy_frontend: image: appleboy/drone-ssh network_mode: host settings: @@ -57,6 +93,8 @@ pipeline: password: from_secret: ssh_password script: - - /opt/docker/TelegramAktienBot/deploy.sh + - /opt/docker/TelegramAktienBot/deploy_frontend.sh + when: + path: "frontend/*" branches: main From 1fe1e0e9cfd47085062ee88dcc6532cd5a628f2f Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 10:42:53 +0200 Subject: [PATCH 068/263] Create bot and admin user when creating tables --- api/app.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/api/app.py b/api/app.py index 933b471..955cc18 100644 --- a/api/app.py +++ b/api/app.py @@ -1,8 +1,11 @@ +import os + from apiflask import APIFlask from dotenv import load_dotenv from flask_cors import CORS +from api.helper_functions import hash_password from models import * from api_blueprint_keyword import keyword_blueprint from api_blueprint_shares import shares_blueprint @@ -35,6 +38,24 @@ def create_app(): def init_database(): db.create_all() + if os.getenv("BOT_USER") is not None and os.getenv("BOT_PASSWORD") is not None: + bot = User( + username=os.getenv("BOT_USER"), + password=hash_password(os.getenv("BOT_PASSWORD")), + admin=False + ) + db.session.add(bot) + db.session.commit() + + if os.getenv("ADMIN_USER") is not None and os.getenv("ADMIN_PASSWORD") is not None: + admin = User( + username=os.getenv("ADMIN_USER"), + password=hash_password(os.getenv("ADMIN_PASSWORD")), + admin=True + ) + db.session.add(admin) + db.session.commit() + return application From 2ba8a9bd13339b656e0c59e6786b24148be62df9 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 10:55:48 +0200 Subject: [PATCH 069/263] Check if user already exist --- api/app.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/api/app.py b/api/app.py index 955cc18..da31695 100644 --- a/api/app.py +++ b/api/app.py @@ -39,22 +39,24 @@ def create_app(): db.create_all() if os.getenv("BOT_USER") is not None and os.getenv("BOT_PASSWORD") is not None: - bot = User( - username=os.getenv("BOT_USER"), - password=hash_password(os.getenv("BOT_PASSWORD")), - admin=False - ) - db.session.add(bot) - db.session.commit() + if db.session.query(User).filter_by(username=os.getenv("BOT_USER")).first() is None: # Check if user already exist + bot = User( + username=os.getenv("BOT_USER"), + password=hash_password(os.getenv("BOT_PASSWORD")), + admin=False + ) + db.session.add(bot) + db.session.commit() if os.getenv("ADMIN_USER") is not None and os.getenv("ADMIN_PASSWORD") is not None: - admin = User( - username=os.getenv("ADMIN_USER"), - password=hash_password(os.getenv("ADMIN_PASSWORD")), - admin=True - ) - db.session.add(admin) - db.session.commit() + if db.session.query(User).filter_by(username=os.getenv("ADMIN_USER")).first() is None: # Check if user already exist + admin = User( + username=os.getenv("ADMIN_USER"), + password=hash_password(os.getenv("ADMIN_PASSWORD")), + admin=True + ) + db.session.add(admin) + db.session.commit() return application From 12d59d69ff80641855bfaa2bd52c8b45d053370d Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 17:23:33 +0200 Subject: [PATCH 070/263] Changed database model --- api/api_blueprint_keyword.py | 25 +++++---- api/api_blueprint_portfolio.py | 23 ++++---- api/api_blueprint_shares.py | 25 +++++---- api/api_blueprint_transactions.py | 14 ++--- api/api_blueprint_user.py | 89 +++++++++++++++---------------- api/app.py | 18 +++---- api/auth.py | 2 +- api/helper_functions.py | 37 ++++++------- api/models.py | 12 ++--- api/schema.py | 45 +++++++++++++--- 10 files changed, 160 insertions(+), 130 deletions(-) diff --git a/api/api_blueprint_keyword.py b/api/api_blueprint_keyword.py index 232a97b..a22abb3 100644 --- a/api/api_blueprint_keyword.py +++ b/api/api_blueprint_keyword.py @@ -3,7 +3,7 @@ import os from apiflask import APIBlueprint, abort from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response +from helper_functions import make_response, get_email_or_abort_401 from auth import auth from schema import KeywordResponseSchema, KeywordSchema, DeleteSuccessfulSchema from models import Keyword @@ -18,17 +18,17 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file @keyword_blueprint.auth_required(auth) @keyword_blueprint.doc(summary="Add new keyword", description="Adds new keyword for current user") def add_keyword(data): - username = get_username_or_abort_401() + email = get_email_or_abort_401() check_if_keyword_data_exists(data) key = data['keyword'] - check_keyword = db.session.query(Keyword).filter_by(keyword=key, user_id=get_user_id_from_username(username)).first() + check_keyword = db.session.query(Keyword).filter_by(keyword=key, email=email).first() if check_keyword is None: # Keyword doesn't exist yet for this user new_keyword = Keyword( - user_id=get_user_id_from_username(username), + email=email, keyword=key ) db.session.add(new_keyword) @@ -45,16 +45,21 @@ def add_keyword(data): @keyword_blueprint.auth_required(auth) @keyword_blueprint.doc(summary="Removes existing keyword", description="Removes existing keyword for current user") def remove_keyword(data): - username = get_username_or_abort_401() + email = get_email_or_abort_401() check_if_keyword_data_exists(data) key = data['keyword'] - db.session.query(Keyword).filter_by(keyword=key, user_id=get_user_id_from_username(username)).delete() - db.session.commit() + check_keyword = db.session.query(Keyword).filter_by(keyword=key, email=email).first() - return make_response({}, 200, "Successfully removed keyword") + if check_keyword is None: + return make_response({}, 500, "Keyword doesn't exist for this user") + else: + db.session.query(Keyword).filter_by(keyword=key, email=email).delete() + db.session.commit() + + return make_response({}, 200, "Successfully removed keyword") @keyword_blueprint.route('/keywords', methods=['GET']) @@ -62,10 +67,10 @@ def remove_keyword(data): @keyword_blueprint.auth_required(auth) @keyword_blueprint.doc(summary="Returns all keywords", description="Returns all keywords for current user") def get_keywords(): - username = get_username_or_abort_401() + email = get_email_or_abort_401() return_keywords = [] - keywords = db.session.query(Keyword).filter_by(user_id=get_user_id_from_username(username)).all() + keywords = db.session.query(Keyword).filter_by(email=email).all() if keywords is not None: for row in keywords: diff --git a/api/api_blueprint_portfolio.py b/api/api_blueprint_portfolio.py index 5970ced..b6ecea3 100644 --- a/api/api_blueprint_portfolio.py +++ b/api/api_blueprint_portfolio.py @@ -2,9 +2,9 @@ import os from apiflask import APIBlueprint +from api.schema import PortfolioResponseSchema from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response -from models import Transaction +from helper_functions import make_response, get_email_or_abort_401 from auth import auth portfolio_blueprint = APIBlueprint('portfolio', __name__, url_prefix='/api') @@ -12,21 +12,22 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file @portfolio_blueprint.route('/portfolio', methods=['GET']) -@portfolio_blueprint.output(200) +@portfolio_blueprint.output(PortfolioResponseSchema(many=True), 200) @portfolio_blueprint.auth_required(auth) @portfolio_blueprint.doc(summary="Returns portfolio", description="Returns all shares of current user") def get_portfolio(): - username = get_username_or_abort_401() + email = get_email_or_abort_401() - return_portfolio = {} - transactions = db.session.query(Transaction).filter_by(user_id=get_user_id_from_username(username)).all() + return_portfolio = [] + transactions = db.session.execute("SELECT symbol, SUM(count), SUM(price), MAX(time) FROM `transactions` WHERE email = '" + email + "' GROUP BY symbol;").all() if transactions is not None: for row in transactions: - if row.symbol in return_portfolio: - return_portfolio[row.symbol]['count'] += row.count - return_portfolio[row.symbol]['last_transaction'] = row.time - else: - return_portfolio[row.symbol] = {"count": row.count, "last_transaction": row.time} + return_portfolio.append({ + "symbol": row[0], + "count": row[1], + # "price": row[2], + "last_transaction": row[3] + }) return make_response(return_portfolio, 200, "Successfully loaded symbols") diff --git a/api/api_blueprint_shares.py b/api/api_blueprint_shares.py index 3e18e8c..1c7c785 100644 --- a/api/api_blueprint_shares.py +++ b/api/api_blueprint_shares.py @@ -4,7 +4,7 @@ from apiflask import APIBlueprint, abort from auth import auth from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response +from helper_functions import make_response, get_email_or_abort_401 from models import Share from schema import SymbolSchema, SymbolResponseSchema, DeleteSuccessfulSchema @@ -18,17 +18,17 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file @shares_blueprint.auth_required(auth) @shares_blueprint.doc(summary="Add new symbol", description="Adds new symbol for current user") def add_symbol(data): - username = get_username_or_abort_401() + email = get_email_or_abort_401() check_if_symbol_data_exists(data) symbol = data['symbol'] - check_share = db.session.query(Share).filter_by(symbol=symbol, user_id=get_user_id_from_username(username)).first() + check_share = db.session.query(Share).filter_by(symbol=symbol, email=email).first() if check_share is None: # Keyword doesn't exist yet for this user new_symbol = Share( - user_id=get_user_id_from_username(username), + email=email, symbol=symbol ) db.session.add(new_symbol) @@ -45,16 +45,21 @@ def add_symbol(data): @shares_blueprint.auth_required(auth) @shares_blueprint.doc(summary="Removes existing symbol", description="Removes existing symbol for current user") def remove_symbol(data): - username = get_username_or_abort_401() + email = get_email_or_abort_401() check_if_symbol_data_exists(data) symbol = data['symbol'] - db.session.query(Share).filter_by(symbol=symbol, user_id=get_user_id_from_username(username)).delete() - db.session.commit() + check_share = db.session.query(Share).filter_by(symbol=symbol, email=email).first() - return make_response({}, 200, "Successfully removed symbol") + if check_share is None: + return make_response({}, 500, "Symbol doesn't exist for this user") + else: + db.session.query(Share).filter_by(symbol=symbol, email=email).delete() + db.session.commit() + + return make_response({}, 200, "Successfully removed symbol") @shares_blueprint.route('/shares', methods=['GET']) @@ -62,10 +67,10 @@ def remove_symbol(data): @shares_blueprint.auth_required(auth) @shares_blueprint.doc(summary="Returns all symbols", description="Returns all symbols for current user") def get_symbol(): - username = get_username_or_abort_401() + email = get_email_or_abort_401() return_symbols = [] - symbols = db.session.query(Share).filter_by(user_id=get_user_id_from_username(username)).all() + symbols = db.session.query(Share).filter_by(email=email).all() if symbols is not None: for row in symbols: diff --git a/api/api_blueprint_transactions.py b/api/api_blueprint_transactions.py index b0e58fc..054a032 100644 --- a/api/api_blueprint_transactions.py +++ b/api/api_blueprint_transactions.py @@ -4,9 +4,9 @@ import datetime from apiflask import abort, APIBlueprint from db import db -from helper_functions import get_user_id_from_username, get_username_or_abort_401, make_response +from helper_functions import make_response, get_email_or_abort_401 from models import Transaction -from schema import TransactionSchema +from schema import TransactionSchema, TransactionResponseSchema from auth import auth transaction_blueprint = APIBlueprint('transaction', __name__, url_prefix='/api') @@ -14,17 +14,17 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file @transaction_blueprint.route('/transaction', methods=['POST']) -@transaction_blueprint.output((), 200) +@transaction_blueprint.output(TransactionResponseSchema(), 200) @transaction_blueprint.input(schema=TransactionSchema) @transaction_blueprint.auth_required(auth) @transaction_blueprint.doc(summary="Adds new transaction", description="Adds new transaction for current user") def add_transaction(data): - username = get_username_or_abort_401() + email = get_email_or_abort_401() check_if_transaction_data_exists(data) new_transaction = Transaction( - user_id=get_user_id_from_username(username), + email=email, symbol=data['symbol'], time=datetime.datetime.strptime(data['time'], '%Y-%m-%dT%H:%M:%S.%fZ'), count=data['count'], @@ -41,10 +41,10 @@ def add_transaction(data): @transaction_blueprint.auth_required(auth) @transaction_blueprint.doc(summary="Returns all transactions", description="Returns all transactions for current user") def get_transaction(): - username = get_username_or_abort_401() + email = get_email_or_abort_401() return_transactions = [] - transactions = db.session.query(Transaction).filter_by(user_id=get_user_id_from_username(username)).all() + transactions = db.session.query(Transaction).filter_by(email=email).all() if transactions is not None: for row in transactions: diff --git a/api/api_blueprint_user.py b/api/api_blueprint_user.py index 747a82f..0afd873 100644 --- a/api/api_blueprint_user.py +++ b/api/api_blueprint_user.py @@ -5,9 +5,9 @@ import jwt from apiflask import APIBlueprint, abort from db import db -from helper_functions import check_password, hash_password, get_username_or_abort_401, abort_if_no_admin, make_response +from helper_functions import check_password, hash_password, abort_if_no_admin, make_response, get_email_or_abort_401 from models import User -from schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema +from schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema, RegisterDataSchema, UpdateUserDataSchema from auth import auth users_blueprint = APIBlueprint('users', __name__, url_prefix='/api') @@ -33,9 +33,9 @@ def users(): @users_blueprint.auth_required(auth) @users_blueprint.doc(summary="Get current user", description="Returns current user") def user(): - username = get_username_or_abort_401() + email = get_email_or_abort_401() - res = db.session.query(User).filter_by(username=username).first().as_dict() + res = db.session.query(User).filter_by(email=email).first().as_dict() return make_response(res, 200, "Successfully received current user data") @@ -45,40 +45,45 @@ def user(): @users_blueprint.input(schema=LoginDataSchema) @users_blueprint.doc(summary="Login", description="Returns jwt token if username and password match, otherwise returns error") def login(data): - check_if_user_data_exists(data) + check_if_email_data_exists(data) + check_if_password_data_exists(data) - username = data['username'] + email = data['email'] password = data['password'] - query_user = db.session.query(User).filter_by(username=username).first() + query_user = db.session.query(User).filter_by(email=email).first() - if query_user is None: # Username doesn't exist + if query_user is None: # email doesn't exist abort(500, message="Unable to login") if not check_password(query_user.password, password): # Password incorrect abort(500, message="Unable to login") - token = jwt.encode({'username': query_user.username, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=45)}, os.getenv('SECRET_KEY'), "HS256") + token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=45)}, os.getenv('SECRET_KEY'), "HS256") return make_response({"token": token}, 200, "Successfully logged in") @users_blueprint.route('/user/register', methods=['POST']) @users_blueprint.output(UsersSchema(), 200) -@users_blueprint.input(schema=LoginDataSchema) +@users_blueprint.input(schema=RegisterDataSchema) @users_blueprint.doc(summary="Register", description="Registers user") def register(data): - check_if_user_data_exists(data) + check_if_email_data_exists(data) + check_if_username_data_exists(data) + check_if_password_data_exists(data) + email = data['email'] username = data['username'] password = data['password'] - query_user = db.session.query(User).filter_by(username=username).first() + query_user = db.session.query(User).filter_by(email=email).first() if query_user is not None: # Username already exist - abort(500, message="Username already exist") + abort(500, message="Email already exist") new_user = User( + email=email, username=username, password=hash_password(password), admin=False @@ -91,26 +96,21 @@ def register(data): @users_blueprint.route('/user', methods=['PUT']) @users_blueprint.output({}, 200) -@users_blueprint.input(schema=LoginDataSchema) +@users_blueprint.input(schema=UpdateUserDataSchema) @users_blueprint.auth_required(auth) @users_blueprint.doc(summary="Update user", description="Changes password and/or username of current user") def update_user(data): - username = get_username_or_abort_401() + email = get_email_or_abort_401() - check_if_user_data_exists(data) - - new_username = data['username'] - new_password = data['password'] - - query_user = db.session.query(User).filter_by(username=username).first() + query_user = db.session.query(User).filter_by(email=email).first() if query_user is None: # Username doesn't exist abort(500, message="Unable to login") - if new_password is not None: - query_user.password = hash_password(new_password) - if new_username is not None: - query_user.username = new_username + if "password" in data and data['password'] is not None: + query_user.password = hash_password(data['password']) + if "username" in data and data['username'] is not None: + query_user.username = data['username'] db.session.commit() @@ -125,12 +125,13 @@ def update_user(data): def set_admin(data): abort_if_no_admin() # Only admin users can do this + check_if_email_data_exists(data) check_if_admin_data_exists(data) - username = data['username'] + email = data['email'] admin = data['admin'] - query_user = db.session.query(User).filter_by(username=username).first() + query_user = db.session.query(User).filter_by(email=email).first() if query_user is None: # Username doesn't exist abort(500, message="Unable to login") @@ -147,29 +148,31 @@ def set_admin(data): @users_blueprint.auth_required(auth) @users_blueprint.doc(summary="Delete user", description="Deletes user by username") def delete_user(data): - check_if_delete_data_exists(data) + check_if_email_data_exists(data) - username = data['username'] + email = data['email'] - if username == get_username_or_abort_401(): # Username is same as current user - db.session.query(User).filter_by(username=username).delete() + if email == get_email_or_abort_401(): # Username is same as current user + db.session.query(User).filter_by(email=email).delete() db.session.commit() else: # Delete different user than my user -> only admin users abort_if_no_admin() - db.session.query(User).filter_by(username=username).delete() + db.session.query(User).filter_by(email=email).delete() db.session.commit() return make_response({}, 200, "Successfully removed user") -def check_if_user_data_exists(data): - if "username" not in data: - abort(400, message="Username missing") +def check_if_email_data_exists(data): + if "email" not in data: + abort(400, message="Email missing") - if data['username'] == "" or data['username'] is None: - abort(400, message="Username missing") + if data['email'] == "" or data['email'] is None: + abort(400, message="Email missing") + +def check_if_password_data_exists(data): if "password" not in data: abort(400, message="Password missing") @@ -177,23 +180,17 @@ def check_if_user_data_exists(data): abort(400, message="Password missing") -def check_if_admin_data_exists(data): +def check_if_username_data_exists(data): if "username" not in data: abort(400, message="Username missing") if data['username'] == "" or data['username'] is None: abort(400, message="Username missing") + +def check_if_admin_data_exists(data): if "admin" not in data: abort(400, message="Admin state missing") if data['admin'] == "" or data['admin'] is None: abort(400, message="Admin state missing") - - -def check_if_delete_data_exists(data): - if "username" not in data: - abort(400, message="Username missing") - - if data['username'] == "" or data['username'] is None: - abort(400, message="Username missing") diff --git a/api/app.py b/api/app.py index da31695..4e1dfa3 100644 --- a/api/app.py +++ b/api/app.py @@ -7,11 +7,7 @@ from flask_cors import CORS from api.helper_functions import hash_password from models import * -from api_blueprint_keyword import keyword_blueprint -from api_blueprint_shares import shares_blueprint from api_blueprint_user import users_blueprint -from api_blueprint_transactions import transaction_blueprint -from api_blueprint_portfolio import portfolio_blueprint def create_app(): @@ -38,20 +34,22 @@ def create_app(): def init_database(): db.create_all() - if os.getenv("BOT_USER") is not None and os.getenv("BOT_PASSWORD") is not None: - if db.session.query(User).filter_by(username=os.getenv("BOT_USER")).first() is None: # Check if user already exist + if os.getenv("BOT_EMAIL") is not None and os.getenv("BOT_USERNAME") is not None and os.getenv("BOT_PASSWORD") is not None: + if db.session.query(User).filter_by(email=os.getenv("BOT_EMAIL")).first() is None: # Check if user already exist bot = User( - username=os.getenv("BOT_USER"), + email=os.getenv("BOT_EMAIL"), + username=os.getenv("BOT_USERNAME"), password=hash_password(os.getenv("BOT_PASSWORD")), admin=False ) db.session.add(bot) db.session.commit() - if os.getenv("ADMIN_USER") is not None and os.getenv("ADMIN_PASSWORD") is not None: - if db.session.query(User).filter_by(username=os.getenv("ADMIN_USER")).first() is None: # Check if user already exist + if os.getenv("ADMIN_EMAIL") is not None and os.getenv("ADMIN_USERNAME") is not None and os.getenv("ADMIN_PASSWORD") is not None: + if db.session.query(User).filter_by(email=os.getenv("ADMIN_EMAIL")).first() is None: # Check if user already exist admin = User( - username=os.getenv("ADMIN_USER"), + email=os.getenv("ADMIN_EMAIL"), + username=os.getenv("ADMIN_USERNAME"), password=hash_password(os.getenv("ADMIN_PASSWORD")), admin=True ) diff --git a/api/auth.py b/api/auth.py index 8096012..f13eda3 100644 --- a/api/auth.py +++ b/api/auth.py @@ -17,5 +17,5 @@ def verify_token(token): try: jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"]) return True - except jwt.exceptions.DecodeError: + except: return False diff --git a/api/helper_functions.py b/api/helper_functions.py index 59991c6..6eca7a5 100644 --- a/api/helper_functions.py +++ b/api/helper_functions.py @@ -32,53 +32,46 @@ def extract_token_data(token): if token is not None: try: return jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"]) - except jwt.exceptions.DecodeError: + except: return None else: return None -def get_username_from_token_data(): +def get_email_from_token_data(): if 'Authorization' in request.headers: token = request.headers['Authorization'].split(" ")[1] if token is not None: if ':' in token: # Maybe bot token, check if token valid and return username after ":" then - username = token.split(":")[1] + email = token.split(":")[1] token = token.split(":")[0] try: - if jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['username'] == "bot": - return username + if jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['email'] == os.getenv("BOT_USER"): + return email else: return None - except jwt.exceptions.DecodeError: + except: return None else: # "Normal" token, extract username from token try: - return jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['username'] - except jwt.exceptions.DecodeError: + return jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['email'] + except: return None return None -def get_user_id_from_username(username): - if username is not None: - return db.session.query(User).filter_by(username=username).first().user_id - else: - return None - - -def get_username_or_abort_401(): +def get_email_or_abort_401(): # get username from jwt token - username = get_username_from_token_data() + email = get_email_from_token_data() - if username is None: # If token not provided or invalid -> return 401 code + if email is None: # If token not provided or invalid -> return 401 code abort(401, message="Unable to login") - return username + return email def abort_if_no_admin(): @@ -87,10 +80,10 @@ def abort_if_no_admin(): def is_user_admin(): - username = get_username_or_abort_401() + email = get_email_or_abort_401() - return db.session.query(User).filter_by(username=username).first().admin + return db.session.query(User).filter_by(email=email).first().admin def make_response(data, status=200, text=""): - return jsonify({"status": status, "text": text, "data": {"token": data}}) + return jsonify({"status": status, "text": text, "data": data}) diff --git a/api/models.py b/api/models.py index 59e4287..20c4830 100644 --- a/api/models.py +++ b/api/models.py @@ -3,10 +3,10 @@ from db import db class User(db.Model): __tablename__ = 'users' - username = db.Column('username', db.String(255), nullable=False, unique=True) + email = db.Column('email', db.String(255), primary_key=True, nullable=False, unique=True) password = db.Column('password', db.String(255), nullable=False, server_default='') - user_id = db.Column('user_id', db.Integer(), primary_key=True) - telegram_name = db.Column('telegram_name', db.String(255), nullable=True, server_default='') + username = db.Column('username', db.String(255), nullable=False, server_default='') + telegram_user_id = db.Column('telegram_user_id', db.String(255), nullable=True, server_default='') admin = db.Column('admin', db.Boolean(), server_default='0') def as_dict(self): @@ -16,7 +16,7 @@ class User(db.Model): class Transaction(db.Model): __tablename__ = 'transactions' t_id = db.Column('t_id', db.Integer(), nullable=False, unique=True, primary_key=True) - user_id = db.Column('user_id', db.Integer(), db.ForeignKey('users.user_id', ondelete='CASCADE')) + email = db.Column('email', db.String(255), db.ForeignKey('users.email', ondelete='CASCADE')) symbol = db.Column('symbol', db.String(255)) time = db.Column('time', db.DateTime()) count = db.Column('count', db.Integer()) @@ -29,7 +29,7 @@ class Transaction(db.Model): class Keyword(db.Model): __tablename__ = 'keywords' s_id = db.Column('s_id', db.Integer(), nullable=False, unique=True, primary_key=True) - user_id = db.Column('user_id', db.Integer(), db.ForeignKey('users.user_id', ondelete='CASCADE')) + email = db.Column('email', db.String(255), db.ForeignKey('users.email', ondelete='CASCADE')) keyword = db.Column('keyword', db.String(255)) def as_dict(self): @@ -39,7 +39,7 @@ class Keyword(db.Model): class Share(db.Model): __tablename__ = 'shares' a_id = db.Column('a_id', db.Integer(), nullable=False, unique=True, primary_key=True) - user_id = db.Column('user_id', db.Integer(), db.ForeignKey('users.user_id', ondelete='CASCADE')) + email = db.Column('email', db.String(255), db.ForeignKey('users.email', ondelete='CASCADE')) symbol = db.Column('symbol', db.String(255)) def as_dict(self): diff --git a/api/schema.py b/api/schema.py index bb1371a..67fdf65 100644 --- a/api/schema.py +++ b/api/schema.py @@ -1,5 +1,7 @@ from apiflask import Schema from apiflask.fields import Integer, String, Boolean, Field, Float +from marshmallow import validate +from marshmallow.fields import Email class BaseResponseSchema(Schema): @@ -11,13 +13,13 @@ class BaseResponseSchema(Schema): class UsersSchema(Schema): admin = Boolean() password = String() - telegram_name = String() - user_id = Integer() username = String() + telegram_user_id = String() + email = Email() class AdminDataSchema(Schema): - username = String() + email = Email() admin = Boolean() @@ -26,12 +28,23 @@ class TokenSchema(Schema): class LoginDataSchema(Schema): + email = Email() + password = String() + + +class RegisterDataSchema(Schema): + email = Email() username = String() password = String() +class UpdateUserDataSchema(Schema): + username = String(required=False) + password = String(required=False) + + class DeleteUserSchema(Schema): - username = String() + email = Email() class ChangePasswordSchema(Schema): @@ -50,7 +63,7 @@ class KeywordSchema(Schema): class KeywordResponseSchema(Schema): keyword = String() s_id = Integer() - user_id = Integer() + email = Email() class SymbolSchema(Schema): @@ -60,7 +73,7 @@ class SymbolSchema(Schema): class SymbolResponseSchema(Schema): symbol = String() s_id = Integer() - user_id = Integer() + email = Email() class PortfolioShareResponseSchema(Schema): @@ -69,12 +82,30 @@ class PortfolioShareResponseSchema(Schema): class TransactionSchema(Schema): - user_id = Integer() + symbol = String() + time = String(validate=validate.Regexp(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z")) + count = Integer() + price = Float() + + +class TransactionResponseSchema(Schema): + email = Email() symbol = String() time = String() count = Integer() price = Float() +class TelegramIdSchema(Schema): + telegram_user_id = String() + + +class PortfolioResponseSchema(Schema): + symbol = String() + last_transaction = String() + count = Integer() + # price = Float() + + class DeleteSuccessfulSchema(Schema): pass From 62d66c1d6f4093ea5db25b72ab33b83c369e19b5 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 17:24:26 +0200 Subject: [PATCH 071/263] Added endpoint to set telegram user id --- api/api_blueprint_telegram.py | 42 +++++++++++++++++++++++++++++++++++ api/app.py | 6 +++++ 2 files changed, 48 insertions(+) create mode 100644 api/api_blueprint_telegram.py diff --git a/api/api_blueprint_telegram.py b/api/api_blueprint_telegram.py new file mode 100644 index 0000000..1aa6f6e --- /dev/null +++ b/api/api_blueprint_telegram.py @@ -0,0 +1,42 @@ +import os + +from apiflask import APIBlueprint, abort + +from db import db +from helper_functions import make_response, get_email_or_abort_401 +from auth import auth +from schema import TelegramIdSchema, UsersSchema +from models import User + +telegram_blueprint = APIBlueprint('telegram', __name__, url_prefix='/api') +__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) + + +@telegram_blueprint.route('/telegram', methods=['POST']) +@telegram_blueprint.output(UsersSchema(many=False), 200) +@telegram_blueprint.input(schema=TelegramIdSchema) +@telegram_blueprint.auth_required(auth) +@telegram_blueprint.doc(summary="Connects telegram user id", description="Connects telegram user id to user account") +def add_keyword(data): + email = get_email_or_abort_401() + + check_if_telegram_user_id_data_exists(data) + + query_user = db.session.query(User).filter_by(email=email).first() + + if query_user is None: # Username doesn't exist + abort(500, message="Unable to login") + + query_user.telegram_user_id = data['telegram_user_id'] + + db.session.commit() + + return make_response(query_user.as_dict(), 200, "Successfully connected telegram user") + + +def check_if_telegram_user_id_data_exists(data): + if "telegram_user_id" not in data: + abort(400, message="User ID missing") + + if data['telegram_user_id'] == "" or data['telegram_user_id'] is None: + abort(400, message="User ID missing") diff --git a/api/app.py b/api/app.py index 4e1dfa3..1132b2e 100644 --- a/api/app.py +++ b/api/app.py @@ -5,6 +5,11 @@ from apiflask import APIFlask from dotenv import load_dotenv from flask_cors import CORS +from api.api_blueprint_keyword import keyword_blueprint +from api.api_blueprint_portfolio import portfolio_blueprint +from api.api_blueprint_shares import shares_blueprint +from api.api_blueprint_transactions import transaction_blueprint +from api.api_blueprint_telegram import telegram_blueprint from api.helper_functions import hash_password from models import * from api_blueprint_user import users_blueprint @@ -29,6 +34,7 @@ def create_app(): application.register_blueprint(transaction_blueprint) application.register_blueprint(portfolio_blueprint) application.register_blueprint(users_blueprint) + application.register_blueprint(telegram_blueprint) @application.before_first_request def init_database(): From 634bc4835dd47530be284c575d17829dab4f9c5a Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 17:50:28 +0200 Subject: [PATCH 072/263] Updated database model --- documentation/database/structure_database.pdf | Bin 1874 -> 0 bytes documentation/database/structure_database.png | Bin 0 -> 38191 bytes documentation/database/structure_database.uxf | 123 ++++-------------- 3 files changed, 22 insertions(+), 101 deletions(-) delete mode 100644 documentation/database/structure_database.pdf create mode 100644 documentation/database/structure_database.png diff --git a/documentation/database/structure_database.pdf b/documentation/database/structure_database.pdf deleted file mode 100644 index fdd0a10cc3d434febb82b15e71dfc2b240f8f22b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1874 zcmaJ?Yfuwc6fOgm8Wj))v5G7j5X#^_@*trYAcR18NJtP-UT%^NL`Z_k62So!L8S#1 z1Z<%&=%|#3yb%;ftqeG*1srs$fTH*;iin8Cs>SXiPO9zn{<-&l_nfoe`OciPc0wNC z8D2xD*xkF|dyPT|AV4kILUD5g`HFZP(}4VV6vud&R4v2608ABw#{w)D28W}?H5jU- zB$ae-2o4&A%+jXn##hC*-;DWo|J21}>CKA1!=?7h0I_kpvRneyoV9LMRd zojW}0mhEYEZ+PxbJ>T&#e9>h1xuZ%C4R&b(&b3{cb@;Ts|0YcmB7E1@Q99Ceihrg) zV(Ec9U54k{Z6(4ygB>mvEeHD7&kvet%2$TXS-eOX@ZADwdxpE)#nmX5Eb<)_W zK7cwtTwv{MdpxAr6udc<02db?9egO`4B89<3x;^7@5(92CgJXaG^1M$TEkx|3e5YM z%RC;ufA_*Udb%W{`V+T6wO~X7~$6=@olL+-ld$ZI>b-i;wXweQJzx0 z<$HQnWYA5YP=W4xpUJ}*VZ=aQbh$ZYVY)5vw>frC=S_GS_jw$m=L@2T&0woS-wK1) zglA3m=arRjF86L`E8YgARl+G3nv`W(o~3c(N{}yOR`>cI>}h*bylyDmHIIMNq9Wi) z=eDDbDJ5Xk5;%CLx-#f=h-5LZ?3*m3Gt`B*K4!-_gAz|ME)@a&A@i0+2riVcsfTu0@(3R6Go`^LJ_mNHourYxwes;6~ZKUsGzXZhRO^h_>~ZGhB% z(cCsx98jOTII7+zporOEJwVMg81S@mlYZHrJYZzaNQgHaT3GkPuJYibc_|gmoA*{4 z3I}V%H(J)MHF3E*XFzw%^=MD2bY59-IDGY=3ZhwQU z<+{=&`==eH)%n>D=cq&t+eF(Mu8M0L&>CzEUo*be;(bx9@z+b7#LBDP!8giQOShQ) zPQg^N*{&l_WS27a-Rh-Q;g|~70%Qk^z>wqw3>2a<7%0ND>O_qc(-JK$)TpIm3@J7|y_0YsF-|l;;piiV0eB{`j~D?E^2FhQeBztrpnw6| z2ZX?csD_{cFlLMcF_{A8s*}PY;>u=003C)RIUF((l0NA*J8Z;>5}c$=@`gXKC!>R0 zREy0Ve6e^PhAX5f=&d4VkwO&%ZcwN^Ra(WzyFM<-Z#J$J1z`OHnS%ZS(m#-NXCM5D zjtM7)6-DY*)Pi2>L?W$!#B$NQ%!uuU;%K}&W=3@Fhy3;NS$g9@wTwrI>cDeFAOwME zFb#oV7-2a<@M;KJ?Ff1ia}`&pRsU~Alv0)`#WW5IvWm3;V-0-`3{)Zz1UVA{5daRM zg@pjzP_8Es;o#{b3~<(A8Z8kw!qHJrEJ=gODMW23gx~A}m<$Gu0my+_3`UrQZsY<~ zdW=R$^ce=zi8MdOAP6R;{S+f|{g*GqW)OOQW=Es_2gV|b`_T^9po(}*L;kD;D!CdU zRThB)9uV#(k+NwR%777!iP6|7o5qmQnRF(~lC#(>CIjKPF=Y~%fx?&+W6>cx6M``Y zB4@A=x}3>|P__#v5)`S`I6&4}1d0{gF@R0zW;dg^^NidSEskpN83|!RGK!s@cMzZQ EH-T`x>;M1& diff --git a/documentation/database/structure_database.png b/documentation/database/structure_database.png new file mode 100644 index 0000000000000000000000000000000000000000..d7d459d1b9e71e0d75fd6f7a1b955ba34ea40f17 GIT binary patch literal 38191 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 z (NRWtHMuz5nn_T=+Tzl%?#1x4Fl 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 - - 10 - - UMLClass - - 580 - 320 - 210 - 80 - - Stichwort +10Database V0.1UMLClass70536021080keywords -- -PK: s_id -FK: username -Stichwort - - - - UMLClass - - 580 - 100 - 210 - 130 - - User +PK: s_id int(11) +FK: email varchar(255) +keyword varchar(255)UMLClass425240210130users -- -PK: username -password -user_id -telegramname -Rolle - - - - - UMLClass - - 930 - 80 - 210 - 90 - - Aktie +PK: email varchar(255) +password varchar(255) +username varchar(255) +telegram_user_id varchar(255) +admin tinyint(1) +UMLClass70515021070shares -- -PK: a_id -FK: username -Aktiensymbol - - - - - UMLClass - - 200 - 320 - 210 - 130 - - Transaktion +PK: a_id int(11) +FK: email varchar(255) +symbol varchar(255) +UMLClass135150210130transaction -- -PK: t_id -FK: username -FK: Aktiensymbol -Zeitpunkt -Anzahl(+/-) -Preis(+/-) - - - - - Relation - - 780 - 120 - 170 - 30 - - lt=<- - 150.0;10.0;10.0;10.0 - - - Relation - - 400 - 120 - 200 - 260 - - lt=<- - 10.0;240.0;180.0;10.0 - - - Relation - - 510 - 200 - 90 - 190 - - lt=<- - 70.0;170.0;10.0;10.0 - - +PK: t_id int(11) +FK: email varchar(255) +symbol varchar(255) +time datetime +count int(11) +price float +Relation335190110100lt=<<- +group=110;10;60;10;60;80;90;80Relation625190100100lt=<<-80;10;50;10;50;80;10;80Relation625260100160lt=<<-80;140;50;140;50;10;10;10UMLClass14041010030Database V0.1 \ No newline at end of file From 836c9f54cf9b60574bb0f800ae39324fc98b7156 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 17:57:22 +0200 Subject: [PATCH 073/263] Updated postman and openapi documentation --- documentation/api/openapi.json | 2 +- documentation/api/postman.json | 334 +++++++++++++++++++++++++-------- 2 files changed, 259 insertions(+), 77 deletions(-) diff --git a/documentation/api/openapi.json b/documentation/api/openapi.json index 7c9d313..f16137b 100644 --- a/documentation/api/openapi.json +++ b/documentation/api/openapi.json @@ -1 +1 @@ -{"components":{"schemas":{"AdminData":{"properties":{"admin":{"type":"boolean"},"username":{"type":"string"}},"type":"object"},"DeleteSuccessful":{"properties":{},"type":"object"},"DeleteUser":{"properties":{"username":{"type":"string"}},"type":"object"},"HTTPError":{"properties":{"detail":{"type":"object"},"message":{"type":"string"}},"type":"object"},"Keyword":{"properties":{"keyword":{"type":"string"}},"type":"object"},"KeywordResponse":{"properties":{"keyword":{"type":"string"},"s_id":{"type":"integer"},"user_id":{"type":"integer"}},"type":"object"},"LoginData":{"properties":{"password":{"type":"string"},"username":{"type":"string"}},"type":"object"},"Symbol":{"properties":{"symbol":{"type":"string"}},"type":"object"},"SymbolResponse":{"properties":{"s_id":{"type":"integer"},"symbol":{"type":"string"},"user_id":{"type":"integer"}},"type":"object"},"Token":{"properties":{"token":{"type":"string"}},"type":"object"},"Transaction":{"properties":{"count":{"type":"integer"},"price":{"type":"number"},"symbol":{"type":"string"},"time":{"type":"string"},"user_id":{"type":"integer"}},"type":"object"},"Users":{"properties":{"admin":{"type":"boolean"},"password":{"type":"string"},"telegram_name":{"type":"string"},"user_id":{"type":"integer"},"username":{"type":"string"}},"type":"object"},"ValidationError":{"properties":{"detail":{"properties":{"":{"properties":{"":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"type":"object"},"message":{"type":"string"}},"type":"object"}},"securitySchemes":{"BearerAuth":{"scheme":"Bearer","type":"http"}}},"info":{"description":"Webengineering 2 | Telegram Aktienbot","title":"APIFlask","version":"0.0.1"},"openapi":"3.0.3","paths":{"/api/keyword":{"delete":{"description":"Removes existing keyword for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Keyword"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/DeleteSuccessful"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Removes existing keyword","tags":["Keyword"]},"post":{"description":"Adds new keyword for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Keyword"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/KeywordResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Add new keyword","tags":["Keyword"]}},"/api/keywords":{"get":{"description":"Returns all keywords for current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/KeywordResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns all keywords","tags":["Keyword"]}},"/api/portfolio":{"get":{"description":"Returns all shares of current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/200"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns portfolio","tags":["Portfolio"]}},"/api/share":{"delete":{"description":"Removes existing symbol for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Symbol"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/DeleteSuccessful"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Removes existing symbol","tags":["Share"]},"post":{"description":"Adds new symbol for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Symbol"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/SymbolResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Add new symbol","tags":["Share"]}},"/api/shares":{"get":{"description":"Returns all symbols for current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/SymbolResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns all symbols","tags":["Share"]}},"/api/transaction":{"post":{"description":"Adds new transaction for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transaction"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/()"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Adds new transaction","tags":["Transaction"]}},"/api/transactions":{"get":{"description":"Returns all transactions for current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Transaction"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns all transactions","tags":["Transaction"]}},"/api/user":{"delete":{"description":"Deletes user by username","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteUser"}}}},"responses":{"204":{"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Delete user","tags":["Users"]},"get":{"description":"Returns current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Users"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Get current user","tags":["Users"]},"put":{"description":"Changes password and/or username of current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginData"}}}},"responses":{"204":{"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Update user","tags":["Users"]}},"/api/user/login":{"post":{"description":"Returns jwt token if username and password match, otherwise returns error","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginData"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Token"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"}},"summary":"Login","tags":["Users"]}},"/api/user/register":{"post":{"description":"Registers user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginData"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Users"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"}},"summary":"Register","tags":["Users"]}},"/api/user/setAdmin":{"put":{"description":"Set admin state of specified user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminData"}}}},"responses":{"204":{"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Set user admin state","tags":["Users"]}},"/api/users":{"get":{"description":"Returns all existing users as array","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Users"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Get all users","tags":["Users"]}}},"servers":[{"name":"Production","url":"https://aktienbot.flokaiser.com"},{"name":"Local","url":"http://127.0.0.1:5000"}],"tags":[{"name":"Keyword"},{"name":"Share"},{"name":"Transaction"},{"name":"Portfolio"},{"name":"Users"}]} +{"components":{"schemas":{"AdminData":{"properties":{"admin":{"type":"boolean"},"email":{"format":"email","type":"string"}},"type":"object"},"DeleteSuccessful":{"properties":{},"type":"object"},"DeleteUser":{"properties":{"email":{"format":"email","type":"string"}},"type":"object"},"HTTPError":{"properties":{"detail":{"type":"object"},"message":{"type":"string"}},"type":"object"},"Keyword":{"properties":{"keyword":{"type":"string"}},"type":"object"},"KeywordResponse":{"properties":{"email":{"format":"email","type":"string"},"keyword":{"type":"string"},"s_id":{"type":"integer"}},"type":"object"},"LoginData":{"properties":{"email":{"format":"email","type":"string"},"password":{"type":"string"}},"type":"object"},"PortfolioResponse":{"properties":{"count":{"type":"integer"},"last_transaction":{"type":"string"},"symbol":{"type":"string"}},"type":"object"},"RegisterData":{"properties":{"email":{"format":"email","type":"string"},"password":{"type":"string"},"username":{"type":"string"}},"type":"object"},"Symbol":{"properties":{"symbol":{"type":"string"}},"type":"object"},"SymbolResponse":{"properties":{"email":{"format":"email","type":"string"},"s_id":{"type":"integer"},"symbol":{"type":"string"}},"type":"object"},"TelegramId":{"properties":{"telegram_user_id":{"type":"string"}},"type":"object"},"Token":{"properties":{"token":{"type":"string"}},"type":"object"},"Transaction":{"properties":{"count":{"type":"integer"},"price":{"type":"number"},"symbol":{"type":"string"},"time":{"pattern":"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z","type":"string"}},"type":"object"},"TransactionResponse":{"properties":{"count":{"type":"integer"},"email":{"format":"email","type":"string"},"price":{"type":"number"},"symbol":{"type":"string"},"time":{"type":"string"}},"type":"object"},"UpdateUserData":{"properties":{"password":{"type":"string"},"username":{"type":"string"}},"type":"object"},"Users":{"properties":{"admin":{"type":"boolean"},"email":{"format":"email","type":"string"},"password":{"type":"string"},"telegram_user_id":{"type":"string"},"username":{"type":"string"}},"type":"object"},"ValidationError":{"properties":{"detail":{"properties":{"":{"properties":{"":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"type":"object"},"message":{"type":"string"}},"type":"object"}},"securitySchemes":{"BearerAuth":{"scheme":"Bearer","type":"http"}}},"info":{"description":"Webengineering 2 | Telegram Aktienbot","title":"APIFlask","version":"0.0.1"},"openapi":"3.0.3","paths":{"/api/keyword":{"delete":{"description":"Removes existing keyword for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Keyword"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/DeleteSuccessful"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Removes existing keyword","tags":["Keyword"]},"post":{"description":"Adds new keyword for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Keyword"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/KeywordResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Add new keyword","tags":["Keyword"]}},"/api/keywords":{"get":{"description":"Returns all keywords for current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/KeywordResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns all keywords","tags":["Keyword"]}},"/api/portfolio":{"get":{"description":"Returns all shares of current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/PortfolioResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns portfolio","tags":["Portfolio"]}},"/api/share":{"delete":{"description":"Removes existing symbol for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Symbol"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/DeleteSuccessful"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Removes existing symbol","tags":["Share"]},"post":{"description":"Adds new symbol for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Symbol"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/SymbolResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Add new symbol","tags":["Share"]}},"/api/shares":{"get":{"description":"Returns all symbols for current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/SymbolResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns all symbols","tags":["Share"]}},"/api/telegram":{"post":{"description":"Connects telegram user id to user account","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelegramId"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Users"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Connects telegram user id","tags":["Telegram"]}},"/api/transaction":{"post":{"description":"Adds new transaction for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transaction"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/TransactionResponse"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Adds new transaction","tags":["Transaction"]}},"/api/transactions":{"get":{"description":"Returns all transactions for current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Transaction"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns all transactions","tags":["Transaction"]}},"/api/user":{"delete":{"description":"Deletes user by username","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteUser"}}}},"responses":{"204":{"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Delete user","tags":["Users"]},"get":{"description":"Returns current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Users"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Get current user","tags":["Users"]},"put":{"description":"Changes password and/or username of current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserData"}}}},"responses":{"204":{"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Update user","tags":["Users"]}},"/api/user/login":{"post":{"description":"Returns jwt token if username and password match, otherwise returns error","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginData"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Token"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"}},"summary":"Login","tags":["Users"]}},"/api/user/register":{"post":{"description":"Registers user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterData"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Users"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"}},"summary":"Register","tags":["Users"]}},"/api/user/setAdmin":{"put":{"description":"Set admin state of specified user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminData"}}}},"responses":{"204":{"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Set user admin state","tags":["Users"]}},"/api/users":{"get":{"description":"Returns all existing users as array","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Users"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Get all users","tags":["Users"]}}},"servers":[{"name":"Production","url":"https://aktienbot.flokaiser.com"},{"name":"Local","url":"http://127.0.0.1:5000"}],"tags":[{"name":"Keyword"},{"name":"Share"},{"name":"Transaction"},{"name":"Portfolio"},{"name":"Users"},{"name":"Telegram"}]} diff --git a/documentation/api/postman.json b/documentation/api/postman.json index e6fee89..db6e014 100644 --- a/documentation/api/postman.json +++ b/documentation/api/postman.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "b478b800-af91-45a7-8713-b199bcc41866", + "_postman_id": "eed3eb48-b932-4d72-979a-38de9ae2cbf0", "name": "APIFlask", "description": "Webengineering 2 | Telegram Aktienbot", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" @@ -35,7 +35,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "raw": "{\n \"keyword\": \"nisi\"\n}", "options": { "raw": { "language": "json" @@ -68,7 +68,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "raw": "{\n \"keyword\": \"nisi\"\n}", "options": { "raw": { "language": "json" @@ -96,7 +96,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {},\n \"status\": 261201,\n \"text\": \"aliqua amet consectetur elit\"\n}" + "body": "{\n \"data\": {},\n \"status\": -35854777,\n \"text\": \"sint in\"\n}" }, { "name": "Validation error", @@ -111,7 +111,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "raw": "{\n \"keyword\": \"nisi\"\n}", "options": { "raw": { "language": "json" @@ -139,7 +139,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" }, { "name": "Authentication error", @@ -154,7 +154,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "raw": "{\n \"keyword\": \"nisi\"\n}", "options": { "raw": { "language": "json" @@ -182,7 +182,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] }, @@ -212,7 +212,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "raw": "{\n \"keyword\": \"nisi\"\n}", "options": { "raw": { "language": "json" @@ -245,7 +245,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "raw": "{\n \"keyword\": \"nisi\"\n}", "options": { "raw": { "language": "json" @@ -273,7 +273,7 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"keyword\": \"tempor dolor commodo ipsum non\",\n \"s_id\": -64068772,\n \"user_id\": 82510382\n },\n {\n \"keyword\": \"in aute ex\",\n \"s_id\": 60028598,\n \"user_id\": -36905324\n }\n ],\n \"status\": 63124172,\n \"text\": \"amet\"\n}" + "body": "{\n \"data\": [\n {\n \"email\": \"8Es0sp5sb@wvQoAOvD.bzz\",\n \"keyword\": \"eiusmo\",\n \"s_id\": -48384117\n },\n {\n \"email\": \"MZ0cnQ1mwRV@oONtzNaRnVSaUXAReoDzwztMr.fiz\",\n \"keyword\": \"ullamco ad\",\n \"s_id\": -36966818\n }\n ],\n \"status\": 55335664,\n \"text\": \"dolor id ex\"\n}" }, { "name": "Validation error", @@ -288,7 +288,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "raw": "{\n \"keyword\": \"nisi\"\n}", "options": { "raw": { "language": "json" @@ -316,7 +316,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" }, { "name": "Authentication error", @@ -331,7 +331,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"keyword\": \"elit Lorem\"\n}", + "raw": "{\n \"keyword\": \"nisi\"\n}", "options": { "raw": { "language": "json" @@ -359,7 +359,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] }, @@ -428,7 +428,7 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"keyword\": \"tempor dolor commodo ipsum non\",\n \"s_id\": -64068772,\n \"user_id\": 82510382\n },\n {\n \"keyword\": \"in aute ex\",\n \"s_id\": 60028598,\n \"user_id\": -36905324\n }\n ],\n \"status\": 63124172,\n \"text\": \"amet\"\n}" + "body": "{\n \"data\": [\n {\n \"email\": \"8Es0sp5sb@wvQoAOvD.bzz\",\n \"keyword\": \"eiusmo\",\n \"s_id\": -48384117\n },\n {\n \"email\": \"MZ0cnQ1mwRV@oONtzNaRnVSaUXAReoDzwztMr.fiz\",\n \"keyword\": \"ullamco ad\",\n \"s_id\": -36966818\n }\n ],\n \"status\": 55335664,\n \"text\": \"dolor id ex\"\n}" }, { "name": "Authentication error", @@ -462,7 +462,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] } @@ -497,7 +497,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", "options": { "raw": { "language": "json" @@ -530,7 +530,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", "options": { "raw": { "language": "json" @@ -558,7 +558,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {},\n \"status\": 261201,\n \"text\": \"aliqua amet consectetur elit\"\n}" + "body": "{\n \"data\": {},\n \"status\": -35854777,\n \"text\": \"sint in\"\n}" }, { "name": "Validation error", @@ -573,7 +573,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", "options": { "raw": { "language": "json" @@ -601,7 +601,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" }, { "name": "Authentication error", @@ -616,7 +616,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", "options": { "raw": { "language": "json" @@ -644,7 +644,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] }, @@ -674,7 +674,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", "options": { "raw": { "language": "json" @@ -707,7 +707,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", "options": { "raw": { "language": "json" @@ -735,7 +735,7 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"s_id\": -53227093,\n \"symbol\": \"veniam ea amet irure\",\n \"user_id\": 7468241\n },\n {\n \"s_id\": -37877246,\n \"symbol\": \"ut tempor labore non\",\n \"user_id\": -72644124\n }\n ],\n \"status\": 87653200,\n \"text\": \"aliquip reprehenderit dolore\"\n}" + "body": "{\n \"data\": [\n {\n \"email\": \"j32mUuC@iIJKfYQWSepoVsngEZYbfEmOwCzXXxVMP.alp\",\n \"s_id\": 86469214,\n \"symbol\": \"labore ea\"\n },\n {\n \"email\": \"PwKotAxaLBa8nY@nSsGmaxZeZqnRhOGmHhbICYhqTGLESrq.xy\",\n \"s_id\": 63768957,\n \"symbol\": \"laboris pariatur commodo\"\n }\n ],\n \"status\": -1862021,\n \"text\": \"culpa nulla\"\n}" }, { "name": "Validation error", @@ -750,7 +750,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", "options": { "raw": { "language": "json" @@ -778,7 +778,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" }, { "name": "Authentication error", @@ -793,7 +793,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"symbol\": \"aliquip ullamco Ut\"\n}", + "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", "options": { "raw": { "language": "json" @@ -821,7 +821,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] }, @@ -890,7 +890,7 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"s_id\": -53227093,\n \"symbol\": \"veniam ea amet irure\",\n \"user_id\": 7468241\n },\n {\n \"s_id\": -37877246,\n \"symbol\": \"ut tempor labore non\",\n \"user_id\": -72644124\n }\n ],\n \"status\": 87653200,\n \"text\": \"aliquip reprehenderit dolore\"\n}" + "body": "{\n \"data\": [\n {\n \"email\": \"j32mUuC@iIJKfYQWSepoVsngEZYbfEmOwCzXXxVMP.alp\",\n \"s_id\": 86469214,\n \"symbol\": \"labore ea\"\n },\n {\n \"email\": \"PwKotAxaLBa8nY@nSsGmaxZeZqnRhOGmHhbICYhqTGLESrq.xy\",\n \"s_id\": 63768957,\n \"symbol\": \"laboris pariatur commodo\"\n }\n ],\n \"status\": -1862021,\n \"text\": \"culpa nulla\"\n}" }, { "name": "Authentication error", @@ -924,7 +924,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] } @@ -959,7 +959,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"count\": 96634001,\n \"price\": -71028444.15053315,\n \"symbol\": \"deserunt officia dolore\",\n \"time\": \"magna occaecat est\",\n \"user_id\": -71272721\n}", + "raw": "{\n \"count\": 76782033,\n \"price\": -9709838.042264134,\n \"symbol\": \"mollit esse ex in\",\n \"time\": \"2120-69-63T43:76:75.378Z\"\n}", "options": { "raw": { "language": "json" @@ -992,7 +992,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"count\": 96634001,\n \"price\": -71028444.15053315,\n \"symbol\": \"deserunt officia dolore\",\n \"time\": \"magna occaecat est\",\n \"user_id\": -71272721\n}", + "raw": "{\n \"count\": 76782033,\n \"price\": -9709838.042264134,\n \"symbol\": \"mollit esse ex in\",\n \"time\": \"2120-69-63T43:76:75.378Z\"\n}", "options": { "raw": { "language": "json" @@ -1020,7 +1020,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {\n \"value\": \"reference #/components/schemas/() not found in the OpenAPI spec\"\n },\n \"status\": 14310780,\n \"text\": \"in sint sit\"\n}" + "body": "{\n \"data\": {\n \"count\": 99691073,\n \"email\": \"DAX6XjHTr8eA6M@xsoUe.id\",\n \"price\": -14617384.086570382,\n \"symbol\": \"eu ad deserunt\",\n \"time\": \"ipsum nisi in laboris\"\n },\n \"status\": -51137224,\n \"text\": \"ipsum culpa amet\"\n}" }, { "name": "Validation error", @@ -1035,7 +1035,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"count\": 96634001,\n \"price\": -71028444.15053315,\n \"symbol\": \"deserunt officia dolore\",\n \"time\": \"magna occaecat est\",\n \"user_id\": -71272721\n}", + "raw": "{\n \"count\": 76782033,\n \"price\": -9709838.042264134,\n \"symbol\": \"mollit esse ex in\",\n \"time\": \"2120-69-63T43:76:75.378Z\"\n}", "options": { "raw": { "language": "json" @@ -1063,7 +1063,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" }, { "name": "Authentication error", @@ -1078,7 +1078,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"count\": 96634001,\n \"price\": -71028444.15053315,\n \"symbol\": \"deserunt officia dolore\",\n \"time\": \"magna occaecat est\",\n \"user_id\": -71272721\n}", + "raw": "{\n \"count\": 76782033,\n \"price\": -9709838.042264134,\n \"symbol\": \"mollit esse ex in\",\n \"time\": \"2120-69-63T43:76:75.378Z\"\n}", "options": { "raw": { "language": "json" @@ -1106,7 +1106,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] }, @@ -1175,7 +1175,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {\n \"count\": -19053100,\n \"price\": 39909986.26075193,\n \"symbol\": \"sit nisi\",\n \"time\": \"pariatur eu proident\",\n \"user_id\": 54782742\n },\n \"status\": 87070947,\n \"text\": \"null\"\n}" + "body": "{\n \"data\": {\n \"count\": -37878076,\n \"price\": -40419772.172105886,\n \"symbol\": \"velit ut Ut elit esse\",\n \"time\": \"5462-92-97T03:27:22.076Z\"\n },\n \"status\": -509477,\n \"text\": \"proident\"\n}" }, { "name": "Authentication error", @@ -1209,7 +1209,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] } @@ -1283,7 +1283,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {\n \"value\": \"reference #/components/schemas/200 not found in the OpenAPI spec\"\n },\n \"status\": 14426134,\n \"text\": \"velit in ad dolore\"\n}" + "body": "{\n \"data\": [\n {\n \"count\": 75537465,\n \"last_transaction\": \"dolor enim\",\n \"symbol\": \"enim nostrud non deserunt\"\n },\n {\n \"count\": -69490418,\n \"last_transaction\": \"et\",\n \"symbol\": \"Excepteur irure est enim\"\n }\n ],\n \"status\": 7099250,\n \"text\": \"dolore in ad\"\n}" }, { "name": "Authentication error", @@ -1317,7 +1317,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] } @@ -1352,7 +1352,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"aliqua dolor do\"\n}", + "raw": "{\n \"email\": \"HqkZQ@KMWQFPMhWGvBHrdzOdOampAHweUvPCej.kvbl\"\n}", "options": { "raw": { "language": "json" @@ -1385,7 +1385,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"aliqua dolor do\"\n}", + "raw": "{\n \"email\": \"HqkZQ@KMWQFPMhWGvBHrdzOdOampAHweUvPCej.kvbl\"\n}", "options": { "raw": { "language": "json" @@ -1428,7 +1428,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"aliqua dolor do\"\n}", + "raw": "{\n \"email\": \"HqkZQ@KMWQFPMhWGvBHrdzOdOampAHweUvPCej.kvbl\"\n}", "options": { "raw": { "language": "json" @@ -1456,7 +1456,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"cupidatat mollit laborum aute\",\n \"aute in laboris dolor\"\n ]\n }\n },\n \"message\": \"in dolore exercitation eu elit\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" }, { "name": "Authentication error", @@ -1471,7 +1471,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"aliqua dolor do\"\n}", + "raw": "{\n \"email\": \"HqkZQ@KMWQFPMhWGvBHrdzOdOampAHweUvPCej.kvbl\"\n}", "options": { "raw": { "language": "json" @@ -1499,7 +1499,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] }, @@ -1568,7 +1568,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {\n \"admin\": false,\n \"password\": \"voluptate magna esse\",\n \"telegram_name\": \"id\",\n \"user_id\": -55336867,\n \"username\": \"do exercitation\"\n },\n \"status\": -11469710,\n \"text\": \"aute\"\n}" + "body": "{\n \"data\": {\n \"admin\": false,\n \"email\": \"VNB@NeqjbTmHnkWDwPacdUoXgpYVTNlD.iip\",\n \"password\": \"fugiat ipsum\",\n \"telegram_user_id\": \"et\",\n \"username\": \"nostrud laborum\"\n },\n \"status\": -78288268,\n \"text\": \"laborum minim\"\n}" }, { "name": "Authentication error", @@ -1602,7 +1602,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] }, @@ -1632,7 +1632,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "raw": "{\n \"password\": \"elit do in esse\",\n \"username\": \"mollit velit sed reprehenderit\"\n}", "options": { "raw": { "language": "json" @@ -1665,7 +1665,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "raw": "{\n \"password\": \"elit do in esse\",\n \"username\": \"mollit velit sed reprehenderit\"\n}", "options": { "raw": { "language": "json" @@ -1708,7 +1708,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "raw": "{\n \"password\": \"elit do in esse\",\n \"username\": \"mollit velit sed reprehenderit\"\n}", "options": { "raw": { "language": "json" @@ -1736,7 +1736,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" }, { "name": "Authentication error", @@ -1751,7 +1751,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "raw": "{\n \"password\": \"elit do in esse\",\n \"username\": \"mollit velit sed reprehenderit\"\n}", "options": { "raw": { "language": "json" @@ -1779,7 +1779,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] }, @@ -1799,7 +1799,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "raw": "{\n \"email\": \"YKQkCdcOoJ3vH@wbOpjGTXH.avm\",\n \"password\": \"veniam\"\n}", "options": { "raw": { "language": "json" @@ -1827,7 +1827,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "raw": "{\n \"email\": \"YKQkCdcOoJ3vH@wbOpjGTXH.avm\",\n \"password\": \"veniam\"\n}", "options": { "raw": { "language": "json" @@ -1856,7 +1856,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {\n \"token\": \"minim\"\n },\n \"status\": 86563099,\n \"text\": \"non tempor quis ullamco est\"\n}" + "body": "{\n \"data\": {\n \"token\": \"do ipsu\"\n },\n \"status\": 31961350,\n \"text\": \"Lorem dolor commodo laborum cillum\"\n}" }, { "name": "Validation error", @@ -1865,7 +1865,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "raw": "{\n \"email\": \"YKQkCdcOoJ3vH@wbOpjGTXH.avm\",\n \"password\": \"veniam\"\n}", "options": { "raw": { "language": "json" @@ -1894,7 +1894,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" } ] }, @@ -1914,7 +1914,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "raw": "{\n \"email\": \"DJR029Ov-keM@pyhCxnpZmcVMxADSiCjmsGRZksCf.zz\",\n \"password\": \"sed voluptate\",\n \"username\": \"voluptate aute proident\"\n}", "options": { "raw": { "language": "json" @@ -1942,7 +1942,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "raw": "{\n \"email\": \"DJR029Ov-keM@pyhCxnpZmcVMxADSiCjmsGRZksCf.zz\",\n \"password\": \"sed voluptate\",\n \"username\": \"voluptate aute proident\"\n}", "options": { "raw": { "language": "json" @@ -1971,7 +1971,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {\n \"admin\": false,\n \"password\": \"voluptate magna esse\",\n \"telegram_name\": \"id\",\n \"user_id\": -55336867,\n \"username\": \"do exercitation\"\n },\n \"status\": -11469710,\n \"text\": \"aute\"\n}" + "body": "{\n \"data\": {\n \"admin\": false,\n \"email\": \"VNB@NeqjbTmHnkWDwPacdUoXgpYVTNlD.iip\",\n \"password\": \"fugiat ipsum\",\n \"telegram_user_id\": \"et\",\n \"username\": \"nostrud laborum\"\n },\n \"status\": -78288268,\n \"text\": \"laborum minim\"\n}" }, { "name": "Validation error", @@ -1980,7 +1980,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"password\": \"ut minim\",\n \"username\": \"esse sit enim consectetur\"\n}", + "raw": "{\n \"email\": \"DJR029Ov-keM@pyhCxnpZmcVMxADSiCjmsGRZksCf.zz\",\n \"password\": \"sed voluptate\",\n \"username\": \"voluptate aute proident\"\n}", "options": { "raw": { "language": "json" @@ -2009,7 +2009,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" } ] }, @@ -2039,7 +2039,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"admin\": true,\n \"username\": \"sunt\"\n}", + "raw": "{\n \"admin\": false,\n \"email\": \"qUgn5u1bWI4cwc@LjiesdFcoYwvweQto.dj\"\n}", "options": { "raw": { "language": "json" @@ -2073,7 +2073,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"admin\": true,\n \"username\": \"sunt\"\n}", + "raw": "{\n \"admin\": false,\n \"email\": \"qUgn5u1bWI4cwc@LjiesdFcoYwvweQto.dj\"\n}", "options": { "raw": { "language": "json" @@ -2117,7 +2117,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"admin\": true,\n \"username\": \"sunt\"\n}", + "raw": "{\n \"admin\": false,\n \"email\": \"qUgn5u1bWI4cwc@LjiesdFcoYwvweQto.dj\"\n}", "options": { "raw": { "language": "json" @@ -2146,7 +2146,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"labore\",\n \"\"\n ]\n }\n },\n \"message\": \"pariatur cillum qui aute\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" }, { "name": "Authentication error", @@ -2161,7 +2161,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"admin\": true,\n \"username\": \"sunt\"\n}", + "raw": "{\n \"admin\": false,\n \"email\": \"qUgn5u1bWI4cwc@LjiesdFcoYwvweQto.dj\"\n}", "options": { "raw": { "language": "json" @@ -2190,7 +2190,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] }, @@ -2259,7 +2259,7 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"admin\": true,\n \"password\": \"incididunt id dolore\",\n \"telegram_name\": \"id sed\",\n \"user_id\": 15906508,\n \"username\": \"dolor consequat ullamco\"\n },\n {\n \"admin\": true,\n \"password\": \"voluptate non\",\n \"telegram_name\": \"dolor sunt fugiat exercitation\",\n \"user_id\": 95246024,\n \"username\": \"Duis qui culpa Ut labore\"\n }\n ],\n \"status\": 81180129,\n \"text\": \"in ullamco\"\n}" + "body": "{\n \"data\": [\n {\n \"admin\": false,\n \"email\": \"paghoo4COL6gQNz@SJXcMSOC.kus\",\n \"password\": \"exercitation laboris Ut cillum nisi\",\n \"telegram_user_id\": \"id in incididunt sint\",\n \"username\": \"sed laborum cill\"\n },\n {\n \"admin\": false,\n \"email\": \"WFYb2q0XXey6WZa@JNSezyFkIVzneuifIXi.la\",\n \"password\": \"velit irure veniam\",\n \"telegram_user_id\": \"pariatur sed commodo\",\n \"username\": \"incididunt consequat\"\n }\n ],\n \"status\": -92106336,\n \"text\": \"commodo ull\"\n}" }, { "name": "Authentication error", @@ -2293,7 +2293,189 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"eiusmod enim ipsum\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + } + ] + } + ] + }, + { + "name": "Telegram", + "item": [ + { + "name": "Connects telegram user id", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"telegram_user_id\": \"do quis\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/telegram", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "telegram" + ] + }, + "description": "Connects telegram user id to user account" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"telegram_user_id\": \"do quis\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/telegram", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "telegram" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": {\n \"admin\": false,\n \"email\": \"VNB@NeqjbTmHnkWDwPacdUoXgpYVTNlD.iip\",\n \"password\": \"fugiat ipsum\",\n \"telegram_user_id\": \"et\",\n \"username\": \"nostrud laborum\"\n },\n \"status\": -78288268,\n \"text\": \"laborum minim\"\n}" + }, + { + "name": "Validation error", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"telegram_user_id\": \"do quis\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/telegram", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "telegram" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"velit\",\n \"ut laboris\"\n ]\n }\n },\n \"message\": \"dolore nostrud officia ex\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"telegram_user_id\": \"do quis\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/telegram", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "telegram" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" } ] } From 0fa5c8fb9ac3765fb4813a77cc5068bbdb71282f Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 18:16:45 +0200 Subject: [PATCH 074/263] Fixed imports --- api/app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/app.py b/api/app.py index 1132b2e..7225166 100644 --- a/api/app.py +++ b/api/app.py @@ -5,12 +5,12 @@ from apiflask import APIFlask from dotenv import load_dotenv from flask_cors import CORS -from api.api_blueprint_keyword import keyword_blueprint -from api.api_blueprint_portfolio import portfolio_blueprint -from api.api_blueprint_shares import shares_blueprint -from api.api_blueprint_transactions import transaction_blueprint -from api.api_blueprint_telegram import telegram_blueprint -from api.helper_functions import hash_password +from api_blueprint_keyword import keyword_blueprint +from api_blueprint_portfolio import portfolio_blueprint +from api_blueprint_shares import shares_blueprint +from api_blueprint_transactions import transaction_blueprint +from api_blueprint_telegram import telegram_blueprint +from helper_functions import hash_password from models import * from api_blueprint_user import users_blueprint From 7d0fdb33fb9c1124b10588383dc0c9b830df7406 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 18:19:51 +0200 Subject: [PATCH 075/263] Fixed imports #2 --- api/api_blueprint_portfolio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api_blueprint_portfolio.py b/api/api_blueprint_portfolio.py index b6ecea3..cf3c9b1 100644 --- a/api/api_blueprint_portfolio.py +++ b/api/api_blueprint_portfolio.py @@ -2,7 +2,7 @@ import os from apiflask import APIBlueprint -from api.schema import PortfolioResponseSchema +from schema import PortfolioResponseSchema from db import db from helper_functions import make_response, get_email_or_abort_401 from auth import auth From cc131a894b956ea24bac50e56e020eb3103bc96d Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Sun, 27 Mar 2022 19:48:59 +0200 Subject: [PATCH 076/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index e2406ae..53ee476 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -98,3 +98,5 @@ pipeline: path: "frontend/*" branches: main +when: + event: [ push ] From c338e8f959995d10af15f9497f8ce3fc6bb3694b Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 20:03:11 +0200 Subject: [PATCH 077/263] Improved try-excepts --- api/auth.py | 2 +- api/helper_functions.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/auth.py b/api/auth.py index f13eda3..d5db292 100644 --- a/api/auth.py +++ b/api/auth.py @@ -17,5 +17,5 @@ def verify_token(token): try: jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"]) return True - except: + except jwt.PyJWTError: return False diff --git a/api/helper_functions.py b/api/helper_functions.py index 6eca7a5..3bb74ee 100644 --- a/api/helper_functions.py +++ b/api/helper_functions.py @@ -32,7 +32,7 @@ def extract_token_data(token): if token is not None: try: return jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"]) - except: + except jwt.PyJWTError: return None else: return None @@ -52,13 +52,13 @@ def get_email_from_token_data(): return email else: return None - except: + except jwt.PyJWTError: return None else: # "Normal" token, extract username from token try: return jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['email'] - except: + except jwt.PyJWTError: return None return None From 3ffaa239e4e4a6fccea9ffad7d62a6e6cac17051 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Sun, 27 Mar 2022 20:04:51 +0200 Subject: [PATCH 078/263] Update pipeline.yml --- .woodpecker/pipeline.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 53ee476..8b80a94 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -96,7 +96,9 @@ pipeline: - /opt/docker/TelegramAktienBot/deploy_frontend.sh when: path: "frontend/*" - + + when: + event: [ push ] + + branches: main -when: - event: [ push ] From d19b38634a70fd59cc145477c610315b248d3a6e Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 20:12:45 +0200 Subject: [PATCH 079/263] Updated schema.py --- api/schema.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/api/schema.py b/api/schema.py index 67fdf65..f425825 100644 --- a/api/schema.py +++ b/api/schema.py @@ -60,16 +60,31 @@ class KeywordSchema(Schema): keyword = String() +class SymbolSchema(Schema): + symbol = String() + + +class TransactionSchema(Schema): + symbol = String() + time = String(validate=validate.Regexp(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z")) + count = Integer() + price = Float() + + +class TelegramIdSchema(Schema): + telegram_user_id = String() + + +class DeleteSuccessfulSchema(Schema): + pass + + class KeywordResponseSchema(Schema): keyword = String() s_id = Integer() email = Email() -class SymbolSchema(Schema): - symbol = String() - - class SymbolResponseSchema(Schema): symbol = String() s_id = Integer() @@ -81,13 +96,6 @@ class PortfolioShareResponseSchema(Schema): last_transaction = String() -class TransactionSchema(Schema): - symbol = String() - time = String(validate=validate.Regexp(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z")) - count = Integer() - price = Float() - - class TransactionResponseSchema(Schema): email = Email() symbol = String() @@ -96,16 +104,8 @@ class TransactionResponseSchema(Schema): price = Float() -class TelegramIdSchema(Schema): - telegram_user_id = String() - - class PortfolioResponseSchema(Schema): symbol = String() last_transaction = String() count = Integer() # price = Float() - - -class DeleteSuccessfulSchema(Schema): - pass From 948ef0c179a3607f2de878d081961ef37bb60164 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 20:24:53 +0200 Subject: [PATCH 080/263] Updated pipeline.yml --- .woodpecker/pipeline.yml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 8b80a94..56f8cf8 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -1,9 +1,19 @@ pipeline: - generate_tag: + generate_docker_tag: image: golang commands: - echo -n "${CI_COMMIT_BRANCH//\//-}-${CI_COMMIT_SHA:0:8}, latest" > .tags - + when: + event: push + + generate_docker_tag_pr: + image: golang + commands: + - echo -n "pr-${CI_COMMIT_SHA:0:8}, pr" > .tags + when: + event: pull_request + + # -------------------------------------- API -------------------------------------- build_api: image: woodpeckerci/plugin-docker-buildx settings: @@ -19,6 +29,7 @@ pipeline: platforms: linux/amd64 when: path: "api/*" + event: push deploy_api: image: appleboy/drone-ssh @@ -34,7 +45,10 @@ pipeline: - /opt/docker/TelegramAktienBot/deploy_api.sh when: path: "api/*" + event: push + + # -------------------------------------- Bot -------------------------------------- build_bot: image: woodpeckerci/plugin-docker-buildx settings: @@ -66,6 +80,8 @@ pipeline: when: path: "telegram_bot/*" + + # -------------------------------------- Frontend -------------------------------------- build_frontend: image: woodpeckerci/plugin-docker-buildx settings: @@ -97,8 +113,4 @@ pipeline: when: path: "frontend/*" - when: - event: [ push ] - - branches: main From 48e97ea40661b79d4f486e5690b31f93e39c346e Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 20:42:11 +0200 Subject: [PATCH 081/263] Updated api to use bcrypt --- api/helper_functions.py | 9 +++------ api/models.py | 2 +- api/requirements.txt | 3 ++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/api/helper_functions.py b/api/helper_functions.py index 3bb74ee..d805602 100644 --- a/api/helper_functions.py +++ b/api/helper_functions.py @@ -1,7 +1,6 @@ -import hashlib import os -import uuid +import bcrypt import jwt from apiflask import abort from flask import request, jsonify @@ -11,13 +10,11 @@ from models import User def hash_password(password): - salt = uuid.uuid4().hex - return hashlib.sha256(salt.encode() + password.encode()).hexdigest() + ':' + salt + return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) def check_password(hashed_password, user_password): - password, salt = hashed_password.split(':') - return password == hashlib.sha256(salt.encode() + user_password.encode()).hexdigest() + return bcrypt.checkpw(hashed_password.encode("utf-8"), user_password) def get_token(): diff --git a/api/models.py b/api/models.py index 20c4830..f63451a 100644 --- a/api/models.py +++ b/api/models.py @@ -4,7 +4,7 @@ from db import db class User(db.Model): __tablename__ = 'users' email = db.Column('email', db.String(255), primary_key=True, nullable=False, unique=True) - password = db.Column('password', db.String(255), nullable=False, server_default='') + password = db.Column('password', db.BINARY(60), nullable=False) username = db.Column('username', db.String(255), nullable=False, server_default='') telegram_user_id = db.Column('telegram_user_id', db.String(255), nullable=True, server_default='') admin = db.Column('admin', db.Boolean(), server_default='0') diff --git a/api/requirements.txt b/api/requirements.txt index bcc5fee..4b9b4eb 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -6,4 +6,5 @@ python-dotenv==0.20.0 pymysql==1.0.2 pyjwt==2.3.0 apiflask==0.12.0 -flask-cors==3.0.10 \ No newline at end of file +flask-cors==3.0.10 +bcrypt==3.1.0 \ No newline at end of file From 5c006528058dbd27c10570a3202a6a0533d238dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Mar 2022 18:43:37 +0000 Subject: [PATCH 082/263] Update python-dotenv requirement in /telegram_bot Updates the requirements on [python-dotenv](https://github.com/theskumar/python-dotenv) to permit the latest version. - [Release notes](https://github.com/theskumar/python-dotenv/releases) - [Changelog](https://github.com/theskumar/python-dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/theskumar/python-dotenv/compare/v0.19.2...v0.20.0) --- updated-dependencies: - dependency-name: python-dotenv dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- telegram_bot/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/requirements.txt b/telegram_bot/requirements.txt index 4dcfe5c..6b0de09 100644 --- a/telegram_bot/requirements.txt +++ b/telegram_bot/requirements.txt @@ -2,5 +2,5 @@ pyTelegramBotAPI~=4.4.0 Markdown~=3.3.6 yfinance~=0.1.70 newsapi-python~=0.2.6 -python-dotenv~=0.19.2 +python-dotenv~=0.20.0 requests~=2.27.1 From d1869cc883030f4abfd01905e53d0d67b0506c46 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Mar 2022 18:46:58 +0000 Subject: [PATCH 083/263] Bump bcrypt from 3.1.0 to 3.2.0 in /api Bumps [bcrypt](https://github.com/pyca/bcrypt) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/pyca/bcrypt/releases) - [Changelog](https://github.com/pyca/bcrypt/blob/main/release.py) - [Commits](https://github.com/pyca/bcrypt/compare/3.1.0...3.2.0) --- updated-dependencies: - dependency-name: bcrypt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- api/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/requirements.txt b/api/requirements.txt index 4b9b4eb..0ab141c 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -7,4 +7,4 @@ pymysql==1.0.2 pyjwt==2.3.0 apiflask==0.12.0 flask-cors==3.0.10 -bcrypt==3.1.0 \ No newline at end of file +bcrypt==3.2.0 \ No newline at end of file From 67e2a4c7610936c3a2a987ac4ba3c100ab6cfeff Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 21:08:24 +0200 Subject: [PATCH 084/263] Fixed binary is not serializable error --- api/models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api/models.py b/api/models.py index f63451a..d85e49c 100644 --- a/api/models.py +++ b/api/models.py @@ -10,7 +10,12 @@ class User(db.Model): admin = db.Column('admin', db.Boolean(), server_default='0') def as_dict(self): - return {c.name: getattr(self, c.name) for c in self.__table__.columns} + return { + "email": self.email, + "username": self.username, + "telegram_user_id": self.telegram_user_id, + "admin": self.admin + } class Transaction(db.Model): From 5dd0cb0b5185ffc2feea13bfacad69e80d4d84d0 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 21:21:20 +0200 Subject: [PATCH 085/263] Fixed binary is not serializable error #2 --- api/api_blueprint_user.py | 2 +- api/helper_functions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/api_blueprint_user.py b/api/api_blueprint_user.py index 0afd873..1a6bccc 100644 --- a/api/api_blueprint_user.py +++ b/api/api_blueprint_user.py @@ -56,7 +56,7 @@ def login(data): if query_user is None: # email doesn't exist abort(500, message="Unable to login") - if not check_password(query_user.password, password): # Password incorrect + if not check_password(query_user.password, password.encode("utf-8")): # Password incorrect abort(500, message="Unable to login") token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=45)}, os.getenv('SECRET_KEY'), "HS256") diff --git a/api/helper_functions.py b/api/helper_functions.py index d805602..3d59264 100644 --- a/api/helper_functions.py +++ b/api/helper_functions.py @@ -14,7 +14,7 @@ def hash_password(password): def check_password(hashed_password, user_password): - return bcrypt.checkpw(hashed_password.encode("utf-8"), user_password) + return bcrypt.checkpw(user_password, hashed_password) def get_token(): From 9c3edfeb8c72b79d5f9c5ce5e86974d9b53af802 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Sun, 27 Mar 2022 21:36:10 +0200 Subject: [PATCH 086/263] Fixed bot account --- api/helper_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/helper_functions.py b/api/helper_functions.py index 3d59264..7f25059 100644 --- a/api/helper_functions.py +++ b/api/helper_functions.py @@ -45,7 +45,7 @@ def get_email_from_token_data(): token = token.split(":")[0] try: - if jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['email'] == os.getenv("BOT_USER"): + if jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['email'] == os.getenv("BOT_EMAIL"): return email else: return None From 879423f7c79a6d61ef93f181d7a62e9133cc9d34 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 28 Mar 2022 17:51:10 +0200 Subject: [PATCH 087/263] Use user id instead of email --- api/helper_functions.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/api/helper_functions.py b/api/helper_functions.py index 7f25059..4ebf10e 100644 --- a/api/helper_functions.py +++ b/api/helper_functions.py @@ -41,12 +41,17 @@ def get_email_from_token_data(): if token is not None: if ':' in token: # Maybe bot token, check if token valid and return username after ":" then - email = token.split(":")[1] + telegram_user_id = token.split(":")[1] token = token.split(":")[0] try: if jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['email'] == os.getenv("BOT_EMAIL"): - return email + res = db.session.query(User).filter_by(telegram_user_id=telegram_user_id).first() + + if res is not None: + return res.as_dict()['email'] + else: + return None else: return None except jwt.PyJWTError: From 482bf70756839eff4aac28e68a5808ebfdc60163 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 28 Mar 2022 17:56:59 +0200 Subject: [PATCH 088/263] increase token expiration for bot user --- api/api_blueprint_user.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/api_blueprint_user.py b/api/api_blueprint_user.py index 1a6bccc..ad2ec2b 100644 --- a/api/api_blueprint_user.py +++ b/api/api_blueprint_user.py @@ -59,7 +59,10 @@ def login(data): if not check_password(query_user.password, password.encode("utf-8")): # Password incorrect abort(500, message="Unable to login") - token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=45)}, os.getenv('SECRET_KEY'), "HS256") + if query_user.email == os.getenv("BOT_EMAIL"): + token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=365)}, os.getenv('SECRET_KEY'), "HS256") + else: + token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1)}, os.getenv('SECRET_KEY'), "HS256") return make_response({"token": token}, 200, "Successfully logged in") From 8d5d777de60031efe5ed2cfd21074d5c9799ae0a Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 28 Mar 2022 18:07:39 +0200 Subject: [PATCH 089/263] implemented apihandler class and functions --- telegram_bot/api_handler.py | 49 ++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/telegram_bot/api_handler.py b/telegram_bot/api_handler.py index 69c4974..aa9b4da 100644 --- a/telegram_bot/api_handler.py +++ b/telegram_bot/api_handler.py @@ -4,4 +4,51 @@ script for communicating with webservice to get data from database __author__ = "Florian Kellermann, Linus Eickhoff" __date__ = "16.03.2022" __version__ = "0.0.1" -__license__ = "None" \ No newline at end of file +__license__ = "None" + +#side-dependencies: none +#Work in Progress + +import sys +import os +import requests as r + +class API_Handler: + #class for communicating with webservice to get data from database + def __init__(self, db_adress): + self.db_adress = db_adress + return + + def get_auth_token(self, email, password): + payload = {'email': email, 'password': password} + with r.Session() as s: + p = s.post(self.db_adress + "/user/login", json=payload) + if p.status_code == 200: + self.token = p.json()["data"]['token'] + return p.json()["data"]['token'] + else: + #print server response + print(p.text) + return None + + def get_user_keywords(self, token, user_id): + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + token + ":" + user_id} + p = s.get(self.db_adress + "/api/keywords", headers=headers) + keywords_json = p.json()["data"] + keywords = [] + for keyword in keywords_json: + keywords.append(keyword["keyword"]) + return keywords + + +if __name__ == "__main__": + + print("This is a module for the telegram bot. It is not intended to be run directly.") + handler = API_Handler("https://aktienbot.flokaiser.com/api") + try: + handler.get_auth_token("bot@example.com", "bot") + except None: + print("Could not connect to server.") + + sys.exit(1) \ No newline at end of file From 01581fa560eedc13006c5bf69f49456e93274453 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 28 Mar 2022 18:09:38 +0200 Subject: [PATCH 090/263] Trigger pipeline --- frontend/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/README.md b/frontend/README.md index 0a4c386..a0a4b06 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -25,3 +25,4 @@ Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To u ## Further help To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. + From 4b23261bd1f7fd2daa6fcb8cd1829aa625804243 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 28 Mar 2022 18:55:52 +0200 Subject: [PATCH 091/263] api_handler functions completed (tbd: testing) --- telegram_bot/api_handler.py | 189 +++++++++++++++++++++++++++++++++--- 1 file changed, 174 insertions(+), 15 deletions(-) diff --git a/telegram_bot/api_handler.py b/telegram_bot/api_handler.py index aa9b4da..45c5de4 100644 --- a/telegram_bot/api_handler.py +++ b/telegram_bot/api_handler.py @@ -15,11 +15,31 @@ import requests as r class API_Handler: #class for communicating with webservice to get data from database - def __init__(self, db_adress): - self.db_adress = db_adress - return + def __init__(self, db_adress, email, password): + """initializes the API_Handler class - def get_auth_token(self, email, password): + Args: + db_adress (string): adress of the database + email (string): email of the user + password (string): password of the user + """ + self.db_adress = db_adress + + payload = {'email': email, 'password': password} + with r.Session() as s: + p = s.post(self.db_adress + "/user/login", json=payload) + if p.status_code == 200: + self.token = p.json()["data"]['token'] + else: + self.token = None + + def reauthorize(self, email, password): + """reauthorizes the user + + Args: + email (string): email of the user + password (string): password of the user + """ payload = {'email': email, 'password': password} with r.Session() as s: p = s.post(self.db_adress + "/user/login", json=payload) @@ -27,28 +47,167 @@ class API_Handler: self.token = p.json()["data"]['token'] return p.json()["data"]['token'] else: - #print server response - print(p.text) + self.token = None return None - def get_user_keywords(self, token, user_id): + def get_user_keywords(self, user_id): + """gets the keywords of the user + + Args: + user_id (int): id of the user + + Returns: + list: list of keywords + """ with r.Session() as s: - headers = {'Authorization': 'Bearer ' + token + ":" + user_id} - p = s.get(self.db_adress + "/api/keywords", headers=headers) - keywords_json = p.json()["data"] + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.get(self.db_adress + "/keywords", headers=headers) + print(req.status_code) + keywords_json = req.json()["data"] keywords = [] for keyword in keywords_json: keywords.append(keyword["keyword"]) + return keywords + def set_keyword(self, user_id, keyword): + """sets the keyword of the user + + Args: + user_id (int): id of the user + keyword (int): keyword of the user + + Returns: + int: status code + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.post(self.db_adress + "/keyword", json={"keyword": keyword}, headers=headers) + + return req.status_code + + def delete_keyword(self, user_id, keyword): + """deletes the keyword of the user + + Args: + user_id (int): id of the user + keyword (string): keyword of the user + + Returns: + int: status code + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.delete(self.db_adress + "/keyword", json={"keyword": keyword}, headers=headers) + + return req.status_code + + def get_user_shares(self, user_id): + """gets the shares of the user + + Args: + user_id (int): id of the user + + Returns: + list: list of shares + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.get(self.db_adress + "/shares", headers=headers) + shares_json = req.json()["data"] + shares = [] + for share in shares_json: + shares.append(share["symbol"]) + + return shares + + def set_share(self, user_id, symbol): + """sets the share of the user + + Args: + user_id (int): id of the user + symbol (string): symbol of the share + + Returns: + int: status code + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.post(self.db_adress + "/share", json={"symbol": symbol}, headers=headers) + return req.status_code + + def delete_share(self, user_id, symbol): + """deletes the share of the user + + Args: + user_id (int): id of the user + symbol (string): symbol of the share + + Returns: + int: status code + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.delete(self.db_adress + "/share", json={"symbol": symbol}, headers=headers) + return req.status_code + + def get_user_transactions(self, user_id): + """gets the transactions of the user + + Args: + user_id (int): id of the user + + Returns: + dict: dictionary of transactions + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.get(self.db_adress + "/transactions", headers=headers) + transactions_dict = dict(req.json()["data"]) + return transactions_dict + + def set_transaction(self, user_id, count, price, symbol, timestamp): + """sets the transaction of the user + + Args: + user_id (int): id of the user + count (int): count of the transaction + price (float): price of the transaction + symbol (string): symbol of the transaction + timestamp (string): timestamp of the transaction + + Returns: + int: status code + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + transaction = {"count": count, "price": price, "symbol": symbol, "time": timestamp} + req = s.post(self.db_adress + "/transaction", json=transaction, headers=headers) + return req.status_code + + def get_user_portfolio(self, user_id): + """gets the portfolio of the user + + Args: + user_id (int): id of the user + + Returns: + dict: dictionary of portfolio + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.get(self.db_adress + "/portfolio", headers=headers) + portfolio_dict = dict(req.json()["data"]) + return portfolio_dict + + + if __name__ == "__main__": print("This is a module for the telegram bot. It is not intended to be run directly.") - handler = API_Handler("https://aktienbot.flokaiser.com/api") - try: - handler.get_auth_token("bot@example.com", "bot") - except None: - print("Could not connect to server.") + handler = API_Handler("https://aktienbot.flokaiser.com/api", "bot@example.com", "bot") + keywords = handler.get_user_keywords(user_id = 1709356058) #user_id is mine (Linus) + print(keywords) sys.exit(1) \ No newline at end of file From c489e9ab46a3a6723669bd483ce59495b3dcc58b Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 28 Mar 2022 19:39:37 +0200 Subject: [PATCH 092/263] added keywords from db (not working currently) --- telegram_bot/api_handler.py | 6 +++--- telegram_bot/bot.py | 32 +++++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/telegram_bot/api_handler.py b/telegram_bot/api_handler.py index 45c5de4..93d5c2f 100644 --- a/telegram_bot/api_handler.py +++ b/telegram_bot/api_handler.py @@ -34,7 +34,7 @@ class API_Handler: self.token = None def reauthorize(self, email, password): - """reauthorizes the user + """set new credentials Args: email (string): email of the user @@ -207,7 +207,7 @@ if __name__ == "__main__": print("This is a module for the telegram bot. It is not intended to be run directly.") handler = API_Handler("https://aktienbot.flokaiser.com/api", "bot@example.com", "bot") - - keywords = handler.get_user_keywords(user_id = 1709356058) #user_id is mine (Linus) + print(handler.token) + keywords = handler.get_user_keywords(user_id = 1709356058) #user_id is currently mine (Linus) print(keywords) sys.exit(1) \ No newline at end of file diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index e3e4f28..c62083f 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -28,12 +28,17 @@ import shares.share_fetcher as share_fetcher from telebot import types from dotenv import load_dotenv +from api_handler import API_Handler + load_dotenv() bot_version = "0.2.1" user_list = [] +#create api handler +api_handler = API_Handler("https://aktienbot.flokaiser.com", os.getenv("BOT_EMAIL"), os.getenv("BOT_PASSWORD")) + class User: # Currently saving users in this class to test functionality -> later database def __init__(self, p_user_id, p_user_name, p_chat_id): @@ -222,11 +227,11 @@ def send_news(message): :rtype: none """ - keyword = "bitcoin" user_id = int(message.from_user.id) - #Get Information for user with this id + keywords = api_handler.get_keywords(user_id) + keyword = keywords[0] - articles = news.get_top_news_by_keyword(keyword) #tbd: get keyword from db + articles = news.get_top_news_by_keyword(keywords[0]) try: formatted_article = news.format_article(articles["articles"][0]) except IndexError: @@ -235,6 +240,27 @@ def send_news(message): bot.send_message(chat_id=user_id, text=f"_keyword: {keyword}_\n\n" + formatted_article, parse_mode="MARKDOWN") +@bot.message_handler(commands=['addkeyword']) +def add_keyword(message): + """ Add keyword to user + :type message: message object bot + :param message: message that was reacted to, in this case always '/addkeyword' + + :raises: none + + :rtype: none + """ + user_id = int(message.from_user.id) + bot.send_message(chat_id=user_id, text='Type keyword to add:') + bot.register_next_step_handler(message, store_keyword) + +def store_keyword(message): + user_id = int(message.from_user.id) + keyword = str(message.text) + api_handler.add_keyword(user_id, keyword) + bot.send_message(chat_id=user_id, text=f'Keyword {keyword} added.') + + @bot.message_handler(func=lambda message: True) # Returning that command is unkown for any other statement def echo_all(message): From 218f70f541ecf74e00ba09739ed73f674a9bca2b Mon Sep 17 00:00:00 2001 From: Rripped Date: Tue, 29 Mar 2022 08:31:58 +0200 Subject: [PATCH 093/263] added status function --- telegram_bot/bot.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index c62083f..5fc363f 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -154,6 +154,21 @@ def send_id(message): bot.reply_to(message, answer) +#function that sends telegram status(running or offline) as message from telegram bot to user +@bot.message_handler(commands=['status']) +def send_status(message): + + """ Sends status to user + :type message: message object bot + :param message: message that was reacted to, if no other command handler gets called + + :raises: none + + :rtype: none + """ + bot.reply_to(message, "bot is running") + + @bot.message_handler(commands=['update']) def send_update(message): @@ -306,9 +321,6 @@ def main_loop(): :rtype: none """ bot.infinity_polling() - while 1: - time.sleep(3) - if __name__ == '__main__': try: From bf8fdbf848a0bf17b1ff40d199fde80aefe71e62 Mon Sep 17 00:00:00 2001 From: Rripped Date: Tue, 29 Mar 2022 08:41:09 +0200 Subject: [PATCH 094/263] formatting and docu additions --- telegram_bot/api_handler.py | 33 ++++++++++++++++++++++++++++++++- telegram_bot/db_handler.py | 15 --------------- 2 files changed, 32 insertions(+), 16 deletions(-) delete mode 100644 telegram_bot/db_handler.py diff --git a/telegram_bot/api_handler.py b/telegram_bot/api_handler.py index 93d5c2f..049ae93 100644 --- a/telegram_bot/api_handler.py +++ b/telegram_bot/api_handler.py @@ -13,8 +13,29 @@ import sys import os import requests as r + + class API_Handler: - #class for communicating with webservice to get data from database + """class for interacting with the api webservice + + Attributes: + db_adress (string): adress of the database + token (string): auth token for api + + Methods: + reauthorize(email, password): set new credentials + get_user_keywords(user_id): gets the keywords of the user + set_keyword(user_id, keyword): sets the keyword of the user + delete_keyword(user_id, keyword): deletes the keyword of the user + get_user_shares(user_id): gets the shares of the user + set_share(user_id, symbol): sets the share of the user + delete_share(user_id, symbol): deletes the share of the user + get_user_transactions(user_id): gets the transactions of the user + set_transaction(user_id, transaction): sets the transaction of the user + delete_transaction(user_id, transaction): deletes the transaction of the user + """ + + def __init__(self, db_adress, email, password): """initializes the API_Handler class @@ -33,6 +54,7 @@ class API_Handler: else: self.token = None + def reauthorize(self, email, password): """set new credentials @@ -50,6 +72,7 @@ class API_Handler: self.token = None return None + def get_user_keywords(self, user_id): """gets the keywords of the user @@ -70,6 +93,7 @@ class API_Handler: return keywords + def set_keyword(self, user_id, keyword): """sets the keyword of the user @@ -86,6 +110,7 @@ class API_Handler: return req.status_code + def delete_keyword(self, user_id, keyword): """deletes the keyword of the user @@ -102,6 +127,7 @@ class API_Handler: return req.status_code + def get_user_shares(self, user_id): """gets the shares of the user @@ -121,6 +147,7 @@ class API_Handler: return shares + def set_share(self, user_id, symbol): """sets the share of the user @@ -136,6 +163,7 @@ class API_Handler: req = s.post(self.db_adress + "/share", json={"symbol": symbol}, headers=headers) return req.status_code + def delete_share(self, user_id, symbol): """deletes the share of the user @@ -151,6 +179,7 @@ class API_Handler: req = s.delete(self.db_adress + "/share", json={"symbol": symbol}, headers=headers) return req.status_code + def get_user_transactions(self, user_id): """gets the transactions of the user @@ -166,6 +195,7 @@ class API_Handler: transactions_dict = dict(req.json()["data"]) return transactions_dict + def set_transaction(self, user_id, count, price, symbol, timestamp): """sets the transaction of the user @@ -185,6 +215,7 @@ class API_Handler: req = s.post(self.db_adress + "/transaction", json=transaction, headers=headers) return req.status_code + def get_user_portfolio(self, user_id): """gets the portfolio of the user diff --git a/telegram_bot/db_handler.py b/telegram_bot/db_handler.py deleted file mode 100644 index 816af71..0000000 --- a/telegram_bot/db_handler.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -script for database interaction -""" -__author__ = "Florian Kellermann, Linus Eickhoff" -__date__ = "15.03.2022" -__version__ = "0.0.1" -__license__ = "None" - -# get db_key from env - -class DB_Handler: - - def __init__(self, db_adress): - # tbd - return \ No newline at end of file From 456bba027b70427bac0b062fe066206eb3fa8026 Mon Sep 17 00:00:00 2001 From: Rripped Date: Tue, 29 Mar 2022 08:44:40 +0200 Subject: [PATCH 095/263] updated env example with bot creds --- telegram_bot/.env.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/telegram_bot/.env.example b/telegram_bot/.env.example index bcd6a02..a36e6ec 100644 --- a/telegram_bot/.env.example +++ b/telegram_bot/.env.example @@ -6,3 +6,7 @@ NEWS_API_KEY= # Flask secret key SECRET_KEY= + +# bot credentials +BOT_EMAIL= +BOT_PASSWORD= \ No newline at end of file From 1ba19a219fe7dac9640cfdfb0490ef23bae5d8f0 Mon Sep 17 00:00:00 2001 From: Rripped Date: Tue, 29 Mar 2022 08:49:32 +0200 Subject: [PATCH 096/263] small change --- telegram_bot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 5fc363f..617b23e 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -31,7 +31,7 @@ from dotenv import load_dotenv from api_handler import API_Handler -load_dotenv() +load_dotenv(dotenv_path='.env') bot_version = "0.2.1" user_list = [] From 21d5d357b861f5cc2c3d4efb7ec97166d7683276 Mon Sep 17 00:00:00 2001 From: Rripped Date: Tue, 29 Mar 2022 09:26:50 +0200 Subject: [PATCH 097/263] moved api handler in subfolder --- telegram_bot/api_handling/__init__.py | 0 telegram_bot/api_handling/api_handler.py | 245 +++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 telegram_bot/api_handling/__init__.py create mode 100644 telegram_bot/api_handling/api_handler.py diff --git a/telegram_bot/api_handling/__init__.py b/telegram_bot/api_handling/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py new file mode 100644 index 0000000..97eb937 --- /dev/null +++ b/telegram_bot/api_handling/api_handler.py @@ -0,0 +1,245 @@ +""" +script for communicating with webservice to get data from database +""" +__author__ = "Florian Kellermann, Linus Eickhoff" +__date__ = "16.03.2022" +__version__ = "0.0.1" +__license__ = "None" + +#side-dependencies: none +#Work in Progress + +import sys +import os +import requests as r + + + +class API_Handler: + """class for interacting with the api webservice + + Attributes: + db_adress (string): adress of the database + token (string): auth token for api + + Methods: + reauthorize(email, password): set new credentials + get_user_keywords(user_id): gets the keywords of the user + set_keyword(user_id, keyword): sets the keyword of the user + delete_keyword(user_id, keyword): deletes the keyword of the user + get_user_shares(user_id): gets the shares of the user + set_share(user_id, symbol): sets the share of the user + delete_share(user_id, symbol): deletes the share of the user + get_user_transactions(user_id): gets the transactions of the user + set_transaction(user_id, transaction): sets the transaction of the user + delete_transaction(user_id, transaction): deletes the transaction of the user + """ + + + def __init__(self, db_adress, email, password): + """initializes the API_Handler class + + Args: + db_adress (string): adress of the database + email (string): email of the user + password (string): password of the user + """ + self.db_adress = db_adress + + payload = {'email': email, 'password': password} + with r.Session() as s: + p = s.post(self.db_adress + "/user/login", json=payload) + if p.status_code == 200: + self.token = p.json()["data"]['token'] + else: + print("Error: " + str(p.status_code) + " invalid credentials") + self.token = None + + + def reauthorize(self, email, password): + """set new credentials + + Args: + email (string): email of the user + password (string): password of the user + """ + payload = {'email': email, 'password': password} + with r.Session() as s: + p = s.post(self.db_adress + "/user/login", json=payload) + if p.status_code == 200: + self.token = p.json()["data"]['token'] + return p.json()["data"]['token'] + else: + self.token = None + return None + + + def get_user_keywords(self, user_id): + """gets the keywords of the user + + Args: + user_id (int): id of the user + + Returns: + list: list of keywords + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.get(self.db_adress + "/keywords", headers=headers) + print(req.status_code) + keywords_json = req.json()["data"] + keywords = [] + for keyword in keywords_json: + keywords.append(keyword["keyword"]) + + return keywords + + + def set_keyword(self, user_id, keyword): + """sets the keyword of the user + + Args: + user_id (int): id of the user + keyword (int): keyword of the user + + Returns: + int: status code + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.post(self.db_adress + "/keyword", json={"keyword": keyword}, headers=headers) + + return req.status_code + + + def delete_keyword(self, user_id, keyword): + """deletes the keyword of the user + + Args: + user_id (int): id of the user + keyword (string): keyword of the user + + Returns: + int: status code + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.delete(self.db_adress + "/keyword", json={"keyword": keyword}, headers=headers) + + return req.status_code + + + def get_user_shares(self, user_id): + """gets the shares of the user + + Args: + user_id (int): id of the user + + Returns: + list: list of shares + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.get(self.db_adress + "/shares", headers=headers) + shares_json = req.json()["data"] + shares = [] + for share in shares_json: + shares.append(share["symbol"]) + + return shares + + + def set_share(self, user_id, symbol): + """sets the share of the user + + Args: + user_id (int): id of the user + symbol (string): symbol of the share + + Returns: + int: status code + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.post(self.db_adress + "/share", json={"symbol": symbol}, headers=headers) + return req.status_code + + + def delete_share(self, user_id, symbol): + """deletes the share of the user + + Args: + user_id (int): id of the user + symbol (string): symbol of the share + + Returns: + int: status code + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.delete(self.db_adress + "/share", json={"symbol": symbol}, headers=headers) + return req.status_code + + + def get_user_transactions(self, user_id): + """gets the transactions of the user + + Args: + user_id (int): id of the user + + Returns: + dict: dictionary of transactions + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.get(self.db_adress + "/transactions", headers=headers) + transactions_dict = dict(req.json()["data"]) + return transactions_dict + + + def set_transaction(self, user_id, count, price, symbol, timestamp): + """sets the transaction of the user + + Args: + user_id (int): id of the user + count (int): count of the transaction + price (float): price of the transaction + symbol (string): symbol of the transaction + timestamp (string): timestamp of the transaction + + Returns: + int: status code + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + transaction = {"count": count, "price": price, "symbol": symbol, "time": timestamp} + req = s.post(self.db_adress + "/transaction", json=transaction, headers=headers) + return req.status_code + + + def get_user_portfolio(self, user_id): + """gets the portfolio of the user + + Args: + user_id (int): id of the user + + Returns: + dict: dictionary of portfolio + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.get(self.db_adress + "/portfolio", headers=headers) + portfolio_dict = dict(req.json()["data"]) + return portfolio_dict + + + + +if __name__ == "__main__": + + print("This is a module for the telegram bot. It is not intended to be run directly.") + handler = API_Handler("https://aktienbot.flokaiser.com/api", "bot@example.com", "bot") + print(handler.token) + keywords = handler.get_user_keywords(user_id = 1709356058) #user_id is currently mine (Linus) + print(keywords) + sys.exit(1) \ No newline at end of file From 4c517998ba0150bb0c877aa5400a7343e0cca8f0 Mon Sep 17 00:00:00 2001 From: Rripped Date: Tue, 29 Mar 2022 10:49:02 +0200 Subject: [PATCH 098/263] added funcs --- telegram_bot/api_handler.py | 244 ------------------------------------ telegram_bot/bot.py | 48 ++++--- 2 files changed, 34 insertions(+), 258 deletions(-) delete mode 100644 telegram_bot/api_handler.py diff --git a/telegram_bot/api_handler.py b/telegram_bot/api_handler.py deleted file mode 100644 index 049ae93..0000000 --- a/telegram_bot/api_handler.py +++ /dev/null @@ -1,244 +0,0 @@ -""" -script for communicating with webservice to get data from database -""" -__author__ = "Florian Kellermann, Linus Eickhoff" -__date__ = "16.03.2022" -__version__ = "0.0.1" -__license__ = "None" - -#side-dependencies: none -#Work in Progress - -import sys -import os -import requests as r - - - -class API_Handler: - """class for interacting with the api webservice - - Attributes: - db_adress (string): adress of the database - token (string): auth token for api - - Methods: - reauthorize(email, password): set new credentials - get_user_keywords(user_id): gets the keywords of the user - set_keyword(user_id, keyword): sets the keyword of the user - delete_keyword(user_id, keyword): deletes the keyword of the user - get_user_shares(user_id): gets the shares of the user - set_share(user_id, symbol): sets the share of the user - delete_share(user_id, symbol): deletes the share of the user - get_user_transactions(user_id): gets the transactions of the user - set_transaction(user_id, transaction): sets the transaction of the user - delete_transaction(user_id, transaction): deletes the transaction of the user - """ - - - def __init__(self, db_adress, email, password): - """initializes the API_Handler class - - Args: - db_adress (string): adress of the database - email (string): email of the user - password (string): password of the user - """ - self.db_adress = db_adress - - payload = {'email': email, 'password': password} - with r.Session() as s: - p = s.post(self.db_adress + "/user/login", json=payload) - if p.status_code == 200: - self.token = p.json()["data"]['token'] - else: - self.token = None - - - def reauthorize(self, email, password): - """set new credentials - - Args: - email (string): email of the user - password (string): password of the user - """ - payload = {'email': email, 'password': password} - with r.Session() as s: - p = s.post(self.db_adress + "/user/login", json=payload) - if p.status_code == 200: - self.token = p.json()["data"]['token'] - return p.json()["data"]['token'] - else: - self.token = None - return None - - - def get_user_keywords(self, user_id): - """gets the keywords of the user - - Args: - user_id (int): id of the user - - Returns: - list: list of keywords - """ - with r.Session() as s: - headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - req = s.get(self.db_adress + "/keywords", headers=headers) - print(req.status_code) - keywords_json = req.json()["data"] - keywords = [] - for keyword in keywords_json: - keywords.append(keyword["keyword"]) - - return keywords - - - def set_keyword(self, user_id, keyword): - """sets the keyword of the user - - Args: - user_id (int): id of the user - keyword (int): keyword of the user - - Returns: - int: status code - """ - with r.Session() as s: - headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - req = s.post(self.db_adress + "/keyword", json={"keyword": keyword}, headers=headers) - - return req.status_code - - - def delete_keyword(self, user_id, keyword): - """deletes the keyword of the user - - Args: - user_id (int): id of the user - keyword (string): keyword of the user - - Returns: - int: status code - """ - with r.Session() as s: - headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - req = s.delete(self.db_adress + "/keyword", json={"keyword": keyword}, headers=headers) - - return req.status_code - - - def get_user_shares(self, user_id): - """gets the shares of the user - - Args: - user_id (int): id of the user - - Returns: - list: list of shares - """ - with r.Session() as s: - headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - req = s.get(self.db_adress + "/shares", headers=headers) - shares_json = req.json()["data"] - shares = [] - for share in shares_json: - shares.append(share["symbol"]) - - return shares - - - def set_share(self, user_id, symbol): - """sets the share of the user - - Args: - user_id (int): id of the user - symbol (string): symbol of the share - - Returns: - int: status code - """ - with r.Session() as s: - headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - req = s.post(self.db_adress + "/share", json={"symbol": symbol}, headers=headers) - return req.status_code - - - def delete_share(self, user_id, symbol): - """deletes the share of the user - - Args: - user_id (int): id of the user - symbol (string): symbol of the share - - Returns: - int: status code - """ - with r.Session() as s: - headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - req = s.delete(self.db_adress + "/share", json={"symbol": symbol}, headers=headers) - return req.status_code - - - def get_user_transactions(self, user_id): - """gets the transactions of the user - - Args: - user_id (int): id of the user - - Returns: - dict: dictionary of transactions - """ - with r.Session() as s: - headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - req = s.get(self.db_adress + "/transactions", headers=headers) - transactions_dict = dict(req.json()["data"]) - return transactions_dict - - - def set_transaction(self, user_id, count, price, symbol, timestamp): - """sets the transaction of the user - - Args: - user_id (int): id of the user - count (int): count of the transaction - price (float): price of the transaction - symbol (string): symbol of the transaction - timestamp (string): timestamp of the transaction - - Returns: - int: status code - """ - with r.Session() as s: - headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - transaction = {"count": count, "price": price, "symbol": symbol, "time": timestamp} - req = s.post(self.db_adress + "/transaction", json=transaction, headers=headers) - return req.status_code - - - def get_user_portfolio(self, user_id): - """gets the portfolio of the user - - Args: - user_id (int): id of the user - - Returns: - dict: dictionary of portfolio - """ - with r.Session() as s: - headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - req = s.get(self.db_adress + "/portfolio", headers=headers) - portfolio_dict = dict(req.json()["data"]) - return portfolio_dict - - - - -if __name__ == "__main__": - - print("This is a module for the telegram bot. It is not intended to be run directly.") - handler = API_Handler("https://aktienbot.flokaiser.com/api", "bot@example.com", "bot") - print(handler.token) - keywords = handler.get_user_keywords(user_id = 1709356058) #user_id is currently mine (Linus) - print(keywords) - sys.exit(1) \ No newline at end of file diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 617b23e..9f75ecf 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -28,7 +28,7 @@ import shares.share_fetcher as share_fetcher from telebot import types from dotenv import load_dotenv -from api_handler import API_Handler +from api_handling.api_handler import API_Handler load_dotenv(dotenv_path='.env') @@ -37,7 +37,8 @@ bot_version = "0.2.1" user_list = [] #create api handler -api_handler = API_Handler("https://aktienbot.flokaiser.com", os.getenv("BOT_EMAIL"), os.getenv("BOT_PASSWORD")) +api_handler = API_Handler("https://aktienbot.flokaiser.com/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD"))) +print(api_handler.token) class User: # Currently saving users in this class to test functionality -> later database def __init__(self, p_user_id, p_user_name, p_chat_id): @@ -243,16 +244,14 @@ def send_news(message): """ user_id = int(message.from_user.id) - keywords = api_handler.get_keywords(user_id) - keyword = keywords[0] + keywords = api_handler.get_user_keywords(user_id) + keyword_search = 'OR'.join(keywords) - articles = news.get_top_news_by_keyword(keywords[0]) - try: - formatted_article = news.format_article(articles["articles"][0]) - except IndexError: - bot.send_message(chat_id=user_id, text=f"no news currently available for keyword: {keyword}") - return - bot.send_message(chat_id=user_id, text=f"_keyword: {keyword}_\n\n" + formatted_article, parse_mode="MARKDOWN") + news_list = api_handler.get_news_for_keyword(keyword_search)['articles'] + + for news in news_list: + formatted_article = news.format_article(news) + bot.send_message(chat_id=user_id, text=formatted_article, parse_mode="MARKDOWN") @bot.message_handler(commands=['addkeyword']) @@ -271,9 +270,30 @@ def add_keyword(message): def store_keyword(message): user_id = int(message.from_user.id) - keyword = str(message.text) - api_handler.add_keyword(user_id, keyword) - bot.send_message(chat_id=user_id, text=f'Keyword {keyword} added.') + print(str(user_id)) + keyword = str(message.text).lower() + api_handler.set_keyword(user_id, keyword) + bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" added.') + +@bot.message_handler(commands=['removekeyword']) +def remove_keyword(message): + """ Remove keyword from user + :type message: message object bot + :param message: message that was reacted to, in this case always '/removekeyword' + + :raises: none + + :rtype: none + """ + user_id = int(message.from_user.id) + bot.send_message(chat_id=user_id, text='Type keyword to remove:') + bot.register_next_step_handler(message, remove_keyword_step) + +def remove_keyword_step(message): + user_id = int(message.from_user.id) + keyword = str(message.text).lower() + api_handler.delete_keyword(user_id, keyword) + bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" removed.') @bot.message_handler(func=lambda message: True) # Returning that command is unkown for any other statement From 80b2ebdaf99bd47d87436feb287586f205a87f7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Mar 2022 09:42:19 +0000 Subject: [PATCH 099/263] Update flask requirement from ~=2.0.3 to ~=2.1.0 in /api Updates the requirements on [flask](https://github.com/pallets/flask) to permit the latest version. - [Release notes](https://github.com/pallets/flask/releases) - [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/flask/compare/2.0.3...2.1.0) --- updated-dependencies: - dependency-name: flask dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- api/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/requirements.txt b/api/requirements.txt index 0ab141c..57da142 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,4 +1,4 @@ -Flask~=2.0.3 +Flask~=2.1.0 python-dotenv==0.20.0 uwsgi==2.0.20 Flask_SQLAlchemy==2.5.1 From 4afd1ab87be8adcff99b2263529eb1b058fe0558 Mon Sep 17 00:00:00 2001 From: Rripped Date: Tue, 29 Mar 2022 12:04:49 +0200 Subject: [PATCH 100/263] workaround for connection problems --- telegram_bot/api_handling/api_handler.py | 22 ++++++++++++++-------- telegram_bot/bot.py | 24 ++++++++++++++++++++---- telegram_bot/news/news_fetcher.py | 7 +++++-- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index 97eb937..8c14833 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -83,16 +83,20 @@ class API_Handler: Returns: list: list of keywords """ + keywords = [] with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} req = s.get(self.db_adress + "/keywords", headers=headers) - print(req.status_code) - keywords_json = req.json()["data"] - keywords = [] - for keyword in keywords_json: - keywords.append(keyword["keyword"]) + if(req.status_code == 200): + keywords_json = req.json()["data"] + for keyword in keywords_json: + keywords.append(keyword["keyword"]) + + return keywords + + else: + return self.get_user_keywords(user_id) # might end in infinite loop!! - return keywords def set_keyword(self, user_id, keyword): @@ -193,8 +197,10 @@ class API_Handler: with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} req = s.get(self.db_adress + "/transactions", headers=headers) - transactions_dict = dict(req.json()["data"]) - return transactions_dict + + if req.status_code == 200: + transactions_dict = dict(req.json()["data"]) + return transactions_dict def set_transaction(self, user_id, count, price, symbol, timestamp): diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 9f75ecf..43d8845 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -245,12 +245,12 @@ def send_news(message): user_id = int(message.from_user.id) keywords = api_handler.get_user_keywords(user_id) - keyword_search = 'OR'.join(keywords) + keywords_search = ','.join(keywords) - news_list = api_handler.get_news_for_keyword(keyword_search)['articles'] + news_list = news.get_top_news_by_keyword(keywords_search)["articles"] - for news in news_list: - formatted_article = news.format_article(news) + for article in news_list: + formatted_article = news.format_article(article) bot.send_message(chat_id=user_id, text=formatted_article, parse_mode="MARKDOWN") @@ -275,6 +275,7 @@ def store_keyword(message): api_handler.set_keyword(user_id, keyword) bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" added.') + @bot.message_handler(commands=['removekeyword']) def remove_keyword(message): """ Remove keyword from user @@ -296,6 +297,21 @@ def remove_keyword_step(message): bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" removed.') +@bot.message_handler(commands=['keywords']) +def send_keywords(message): + """ Send keywords of user + :type message: message object bot + :param message: message that was reacted to, in this case always '/keywords' + + :raises: none + + :rtype: none + """ + user_id = int(message.from_user.id) + keywords = api_handler.get_user_keywords(user_id) + bot.send_message(chat_id=user_id, text=f'Your keywords are: {keywords}') + + @bot.message_handler(func=lambda message: True) # Returning that command is unkown for any other statement def echo_all(message): diff --git a/telegram_bot/news/news_fetcher.py b/telegram_bot/news/news_fetcher.py index 69c0807..e320936 100644 --- a/telegram_bot/news/news_fetcher.py +++ b/telegram_bot/news/news_fetcher.py @@ -6,6 +6,7 @@ __date__ = "15.03.2022" __version__ = "0.0.1" __license__ = "None" +from ast import parse import sys import os import json @@ -13,6 +14,7 @@ import requests from newsapi import NewsApiClient from dotenv import load_dotenv +import urllib.parse as urlparse load_dotenv() @@ -31,8 +33,9 @@ def get_top_news_by_keyword(keyword): Returns: JSON/dict: dict containing articles - """ - top_headlines = newsapi.get_top_headlines(q=keyword, sources=str_sources, language='en') + """ + keyword_url = urlparse.quote(keyword) + top_headlines = newsapi.get_top_headlines(q=keyword_url, sources=str_sources, language='en') return top_headlines def format_article(article): From 4bd53c01169fea417372efa1d4c586641cb1f41d Mon Sep 17 00:00:00 2001 From: Rripped Date: Tue, 29 Mar 2022 12:20:58 +0200 Subject: [PATCH 101/263] news working --- telegram_bot/bot.py | 15 ++++++++++----- telegram_bot/news/news_fetcher.py | 4 +--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 43d8845..c1f21f2 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -245,13 +245,18 @@ def send_news(message): user_id = int(message.from_user.id) keywords = api_handler.get_user_keywords(user_id) - keywords_search = ','.join(keywords) - + keywords_search = ' OR '.join(keywords) + print(keywords_search) news_list = news.get_top_news_by_keyword(keywords_search)["articles"] - for article in news_list: - formatted_article = news.format_article(article) - bot.send_message(chat_id=user_id, text=formatted_article, parse_mode="MARKDOWN") + if news_list: + for article in news_list: + formatted_article = news.format_article(article) + bot.send_message(chat_id=user_id, text=formatted_article, parse_mode="MARKDOWN") + else: + bot.send_message(chat_id=user_id, text='No news found for your keywords.') + + @bot.message_handler(commands=['addkeyword']) diff --git a/telegram_bot/news/news_fetcher.py b/telegram_bot/news/news_fetcher.py index e320936..bf2aeb9 100644 --- a/telegram_bot/news/news_fetcher.py +++ b/telegram_bot/news/news_fetcher.py @@ -14,7 +14,6 @@ import requests from newsapi import NewsApiClient from dotenv import load_dotenv -import urllib.parse as urlparse load_dotenv() @@ -34,8 +33,7 @@ def get_top_news_by_keyword(keyword): Returns: JSON/dict: dict containing articles """ - keyword_url = urlparse.quote(keyword) - top_headlines = newsapi.get_top_headlines(q=keyword_url, sources=str_sources, language='en') + top_headlines = newsapi.get_everything(q=keyword, sources=str_sources, language='en') return top_headlines def format_article(article): From 4005e8dccf696cd81c9581825841dcb3ecf6c17e Mon Sep 17 00:00:00 2001 From: Rripped Date: Tue, 29 Mar 2022 12:28:28 +0200 Subject: [PATCH 102/263] fetching news by keywords --- telegram_bot/bot.py | 2 +- telegram_bot/news/news_fetcher.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index c1f21f2..2fcc146 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -247,7 +247,7 @@ def send_news(message): keywords = api_handler.get_user_keywords(user_id) keywords_search = ' OR '.join(keywords) print(keywords_search) - news_list = news.get_top_news_by_keyword(keywords_search)["articles"] + news_list = news.get_all_news_by_keyword(keywords_search)["articles"][:5] if news_list: for article in news_list: diff --git a/telegram_bot/news/news_fetcher.py b/telegram_bot/news/news_fetcher.py index bf2aeb9..b10d390 100644 --- a/telegram_bot/news/news_fetcher.py +++ b/telegram_bot/news/news_fetcher.py @@ -25,8 +25,8 @@ sources = source_json["sources"] str_sources = ",".join([source["id"] for source in sources]) -def get_top_news_by_keyword(keyword): - """get top news to keyword +def get_all_news_by_keyword(keyword, from_date="2022-01-01", to_date="2022-03-29"): # hard coded will change soon + """get all news to keyword Args: keyword (String): keyword for search From 0aee85d718fe4f7dd2b806c43e1b6410bd0afc4f Mon Sep 17 00:00:00 2001 From: Rripped Date: Tue, 29 Mar 2022 12:29:13 +0200 Subject: [PATCH 103/263] bug fix --- telegram_bot/news/news_fetcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/news/news_fetcher.py b/telegram_bot/news/news_fetcher.py index b10d390..f8764ba 100644 --- a/telegram_bot/news/news_fetcher.py +++ b/telegram_bot/news/news_fetcher.py @@ -56,6 +56,6 @@ if __name__ == '__main__': print("fetching top news by keyword business...") - articles = get_top_news_by_keyword("bitcoin") + articles = get_all_news_by_keyword("bitcoin") formatted_article = format_article(articles["articles"][0]) print(formatted_article) \ No newline at end of file From 6289e5dc71d37d4b5ef91a71959bcd9c9b42a72d Mon Sep 17 00:00:00 2001 From: Rripped Date: Tue, 29 Mar 2022 13:37:44 +0200 Subject: [PATCH 104/263] added date parameter --- telegram_bot/bot.py | 7 ++++++- telegram_bot/news/news_fetcher.py | 9 ++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 2fcc146..b24599a 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -24,6 +24,7 @@ import json import news.news_fetcher as news import shares.share_fetcher as share_fetcher +import datetime as dt from telebot import types from dotenv import load_dotenv @@ -247,7 +248,11 @@ def send_news(message): keywords = api_handler.get_user_keywords(user_id) keywords_search = ' OR '.join(keywords) print(keywords_search) - news_list = news.get_all_news_by_keyword(keywords_search)["articles"][:5] + now = dt.datetime.now().date() + from_date = now - dt.timedelta(days=7) + from_date_formatted = dt.datetime.strftime(from_date, '%Y-%m-%d') + print(from_date_formatted) + news_list = news.get_all_news_by_keyword(keywords_search, from_date_formatted)["articles"][:5] if news_list: for article in news_list: diff --git a/telegram_bot/news/news_fetcher.py b/telegram_bot/news/news_fetcher.py index f8764ba..f913257 100644 --- a/telegram_bot/news/news_fetcher.py +++ b/telegram_bot/news/news_fetcher.py @@ -11,6 +11,7 @@ import sys import os import json import requests +import datetime as dt from newsapi import NewsApiClient from dotenv import load_dotenv @@ -25,15 +26,16 @@ sources = source_json["sources"] str_sources = ",".join([source["id"] for source in sources]) -def get_all_news_by_keyword(keyword, from_date="2022-01-01", to_date="2022-03-29"): # hard coded will change soon +def get_all_news_by_keyword(keyword, from_date="2000-01-01"): # hard coded will change soon """get all news to keyword Args: keyword (String): keyword for search + from_date (String): min date for search Returns: JSON/dict: dict containing articles """ - top_headlines = newsapi.get_everything(q=keyword, sources=str_sources, language='en') + top_headlines = newsapi.get_everything(q=keyword, sources=str_sources, language='en', from_param=from_date) return top_headlines def format_article(article): @@ -54,7 +56,8 @@ def format_article(article): if __name__ == '__main__': - print("fetching top news by keyword business...") + print("this is a module and should not be run directly") + print("fetching top news by keyword bitcoin...") articles = get_all_news_by_keyword("bitcoin") formatted_article = format_article(articles["articles"][0]) From 21d2bc334cb189e789373362290c2e9a467469c5 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Wed, 30 Mar 2022 10:46:54 +0200 Subject: [PATCH 105/263] Tests - Improved directory structure - Added functional and unit tests --- api/app.py | 76 +-- api/app/__init__.py | 67 +++ api/{ => app}/auth.py | 4 +- api/app/blueprints/__init__.py | 0 .../blueprints/keyword.py} | 24 +- .../blueprints/portfolio.py} | 8 +- .../blueprints/shares.py} | 26 +- .../blueprints/telegram.py} | 24 +- .../blueprints/transactions.py} | 54 +- .../blueprints/user.py} | 79 ++- api/app/config/flask.cfg | 26 + api/app/config/flask_test.cfg | 26 + api/{ => app}/db.py | 2 +- api/{ => app}/helper_functions.py | 35 +- api/{ => app}/models.py | 2 +- api/{ => app}/schema.py | 0 api/config.py | 55 -- api/requirements.txt | 4 +- api/tests/conftest.py | 108 ++++ api/tests/functional/__init__.py | 0 api/tests/functional/test_keyword.py | 191 +++++++ api/tests/functional/test_portfolio.py | 59 ++ api/tests/functional/test_share.py | 191 +++++++ api/tests/functional/test_telegram.py | 66 +++ api/tests/functional/test_transaction.py | 103 ++++ api/tests/functional/test_user.py | 513 ++++++++++++++++++ api/tests/pytest.ini | 0 api/tests/unit/__init__.py | 0 api/tests/unit/test_auth.py | 14 + api/tests/unit/test_helper_functions.py | 67 +++ api/tests/unit/test_models.py | 116 ++++ api/tests/unit/test_transaction.py | 40 ++ api/tests/unit/test_user.py | 40 ++ 33 files changed, 1782 insertions(+), 238 deletions(-) create mode 100644 api/app/__init__.py rename api/{ => app}/auth.py (73%) create mode 100644 api/app/blueprints/__init__.py rename api/{api_blueprint_keyword.py => app/blueprints/keyword.py} (85%) rename api/{api_blueprint_portfolio.py => app/blueprints/portfolio.py} (85%) rename api/{api_blueprint_shares.py => app/blueprints/shares.py} (83%) rename api/{api_blueprint_telegram.py => app/blueprints/telegram.py} (73%) rename api/{api_blueprint_transactions.py => app/blueprints/transactions.py} (67%) rename api/{api_blueprint_user.py => app/blueprints/user.py} (75%) create mode 100644 api/app/config/flask.cfg create mode 100644 api/app/config/flask_test.cfg rename api/{ => app}/db.py (63%) rename api/{ => app}/helper_functions.py (72%) rename api/{ => app}/models.py (98%) rename api/{ => app}/schema.py (100%) delete mode 100644 api/config.py create mode 100644 api/tests/conftest.py create mode 100644 api/tests/functional/__init__.py create mode 100644 api/tests/functional/test_keyword.py create mode 100644 api/tests/functional/test_portfolio.py create mode 100644 api/tests/functional/test_share.py create mode 100644 api/tests/functional/test_telegram.py create mode 100644 api/tests/functional/test_transaction.py create mode 100644 api/tests/functional/test_user.py create mode 100644 api/tests/pytest.ini create mode 100644 api/tests/unit/__init__.py create mode 100644 api/tests/unit/test_auth.py create mode 100644 api/tests/unit/test_helper_functions.py create mode 100644 api/tests/unit/test_models.py create mode 100644 api/tests/unit/test_transaction.py create mode 100644 api/tests/unit/test_user.py diff --git a/api/app.py b/api/app.py index 7225166..0424fca 100644 --- a/api/app.py +++ b/api/app.py @@ -1,72 +1,6 @@ -import os +from app import create_app -from apiflask import APIFlask - -from dotenv import load_dotenv -from flask_cors import CORS - -from api_blueprint_keyword import keyword_blueprint -from api_blueprint_portfolio import portfolio_blueprint -from api_blueprint_shares import shares_blueprint -from api_blueprint_transactions import transaction_blueprint -from api_blueprint_telegram import telegram_blueprint -from helper_functions import hash_password -from models import * -from api_blueprint_user import users_blueprint - - -def create_app(): - load_dotenv() - - # Create Flask app load app.config - application = APIFlask(__name__, openapi_blueprint_url_prefix='/api') - application.config.from_object("config.ConfigClass") - - CORS(application, resources={r"*": {"origins": "*"}}) - - application.app_context().push() - - db.init_app(application) - - # api blueprints - application.register_blueprint(keyword_blueprint) - application.register_blueprint(shares_blueprint) - application.register_blueprint(transaction_blueprint) - application.register_blueprint(portfolio_blueprint) - application.register_blueprint(users_blueprint) - application.register_blueprint(telegram_blueprint) - - @application.before_first_request - def init_database(): - db.create_all() - - if os.getenv("BOT_EMAIL") is not None and os.getenv("BOT_USERNAME") is not None and os.getenv("BOT_PASSWORD") is not None: - if db.session.query(User).filter_by(email=os.getenv("BOT_EMAIL")).first() is None: # Check if user already exist - bot = User( - email=os.getenv("BOT_EMAIL"), - username=os.getenv("BOT_USERNAME"), - password=hash_password(os.getenv("BOT_PASSWORD")), - admin=False - ) - db.session.add(bot) - db.session.commit() - - if os.getenv("ADMIN_EMAIL") is not None and os.getenv("ADMIN_USERNAME") is not None and os.getenv("ADMIN_PASSWORD") is not None: - if db.session.query(User).filter_by(email=os.getenv("ADMIN_EMAIL")).first() is None: # Check if user already exist - admin = User( - email=os.getenv("ADMIN_EMAIL"), - username=os.getenv("ADMIN_USERNAME"), - password=hash_password(os.getenv("ADMIN_PASSWORD")), - admin=True - ) - db.session.add(admin) - db.session.commit() - - return application - - -app = create_app() - -# Start development web server -if __name__ == '__main__': - app.run() +# Call the application factory function to construct a Flask application +# instance using the development configuration +application = create_app('config/flask.cfg') +application.run() diff --git a/api/app/__init__.py b/api/app/__init__.py new file mode 100644 index 0000000..c62e70c --- /dev/null +++ b/api/app/__init__.py @@ -0,0 +1,67 @@ +from flask import current_app +from apiflask import APIFlask + +from dotenv import load_dotenv +from flask_cors import CORS + +from app.blueprints.keyword import keyword_blueprint +from app.blueprints.portfolio import portfolio_blueprint +from app.blueprints.shares import shares_blueprint +from app.blueprints.transactions import transaction_blueprint +from app.blueprints.telegram import telegram_blueprint +from app.blueprints.user import users_blueprint +from app.helper_functions import hash_password +from app.models import * + + +def create_app(config_filename=None): + load_dotenv() + + # Create Flask app load app.config + application = APIFlask(__name__, openapi_blueprint_url_prefix='/api') + application.config.from_pyfile(config_filename) + + CORS(application, resources={r"*": {"origins": "*"}}) + + application.app_context().push() + + db.init_app(application) + + # api blueprints + application.register_blueprint(keyword_blueprint) + application.register_blueprint(shares_blueprint) + application.register_blueprint(transaction_blueprint) + application.register_blueprint(portfolio_blueprint) + application.register_blueprint(users_blueprint) + application.register_blueprint(telegram_blueprint) + + @application.before_first_request + def init_database(): + db.create_all() + + if current_app.config['BOT_EMAIL'] is not None and current_app.config['BOT_USERNAME'] is not None and current_app.config['BOT_PASSWORD'] is not None: + if db.session.query(User).filter_by(email=current_app.config['BOT_EMAIL']).first() is None: # Check if user already exist + bot = User( + email=current_app.config['BOT_EMAIL'], + username=current_app.config['BOT_USERNAME'], + password=hash_password(current_app.config['BOT_PASSWORD']), + admin=False + ) + db.session.add(bot) + db.session.commit() + + if current_app.config['ADMIN_EMAIL'] is not None and current_app.config['ADMIN_USERNAME'] is not None and current_app.config['ADMIN_PASSWORD'] is not None: + if db.session.query(User).filter_by(email=current_app.config['ADMIN_EMAIL']).first() is None: # Check if user already exist + admin = User( + email=current_app.config['ADMIN_EMAIL'], + username=current_app.config['ADMIN_USERNAME'], + password=hash_password(current_app.config['ADMIN_PASSWORD']), + admin=True + ) + db.session.add(admin) + db.session.commit() + + return application + + +app = create_app("config/flask.cfg") diff --git a/api/auth.py b/api/app/auth.py similarity index 73% rename from api/auth.py rename to api/app/auth.py index d5db292..513a889 100644 --- a/api/auth.py +++ b/api/app/auth.py @@ -1,4 +1,4 @@ -import os +from flask import current_app import jwt from apiflask import HTTPTokenAuth @@ -15,7 +15,7 @@ def verify_token(token): token = token.split(":")[0] try: - jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"]) + jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"]) return True except jwt.PyJWTError: return False diff --git a/api/app/blueprints/__init__.py b/api/app/blueprints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/api_blueprint_keyword.py b/api/app/blueprints/keyword.py similarity index 85% rename from api/api_blueprint_keyword.py rename to api/app/blueprints/keyword.py index a22abb3..cd00c53 100644 --- a/api/api_blueprint_keyword.py +++ b/api/app/blueprints/keyword.py @@ -2,11 +2,11 @@ import os from apiflask import APIBlueprint, abort -from db import db -from helper_functions import make_response, get_email_or_abort_401 -from auth import auth -from schema import KeywordResponseSchema, KeywordSchema, DeleteSuccessfulSchema -from models import Keyword +from app.db import database as db +from app.helper_functions import make_response, get_email_or_abort_401 +from app.auth import auth +from app.schema import KeywordResponseSchema, KeywordSchema, DeleteSuccessfulSchema +from app.models import Keyword keyword_blueprint = APIBlueprint('keyword', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -20,7 +20,8 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file def add_keyword(data): email = get_email_or_abort_401() - check_if_keyword_data_exists(data) + if not check_if_keyword_data_exists(data): + abort(400, message="Keyword missing") key = data['keyword'] @@ -47,14 +48,15 @@ def add_keyword(data): def remove_keyword(data): email = get_email_or_abort_401() - check_if_keyword_data_exists(data) + if not check_if_keyword_data_exists(data): + abort(400, message="Keyword missing") key = data['keyword'] check_keyword = db.session.query(Keyword).filter_by(keyword=key, email=email).first() if check_keyword is None: - return make_response({}, 500, "Keyword doesn't exist for this user") + return abort(500, "Keyword doesn't exist for this user") else: db.session.query(Keyword).filter_by(keyword=key, email=email).delete() db.session.commit() @@ -81,7 +83,9 @@ def get_keywords(): def check_if_keyword_data_exists(data): if "keyword" not in data: - abort(400, message="Keyword missing") + return False if data['keyword'] == "" or data['keyword'] is None: - abort(400, message="Keyword missing") + return False + + return True diff --git a/api/api_blueprint_portfolio.py b/api/app/blueprints/portfolio.py similarity index 85% rename from api/api_blueprint_portfolio.py rename to api/app/blueprints/portfolio.py index cf3c9b1..465ce47 100644 --- a/api/api_blueprint_portfolio.py +++ b/api/app/blueprints/portfolio.py @@ -2,10 +2,10 @@ import os from apiflask import APIBlueprint -from schema import PortfolioResponseSchema -from db import db -from helper_functions import make_response, get_email_or_abort_401 -from auth import auth +from app.schema import PortfolioResponseSchema +from app.db import database as db +from app.helper_functions import make_response, get_email_or_abort_401 +from app.auth import auth portfolio_blueprint = APIBlueprint('portfolio', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) diff --git a/api/api_blueprint_shares.py b/api/app/blueprints/shares.py similarity index 83% rename from api/api_blueprint_shares.py rename to api/app/blueprints/shares.py index 1c7c785..24656a1 100644 --- a/api/api_blueprint_shares.py +++ b/api/app/blueprints/shares.py @@ -2,11 +2,11 @@ import os from apiflask import APIBlueprint, abort -from auth import auth -from db import db -from helper_functions import make_response, get_email_or_abort_401 -from models import Share -from schema import SymbolSchema, SymbolResponseSchema, DeleteSuccessfulSchema +from app.auth import auth +from app.db import database as db +from app.helper_functions import make_response, get_email_or_abort_401 +from app.models import Share +from app.schema import SymbolSchema, SymbolResponseSchema, DeleteSuccessfulSchema shares_blueprint = APIBlueprint('share', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -20,7 +20,8 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file def add_symbol(data): email = get_email_or_abort_401() - check_if_symbol_data_exists(data) + if not check_if_symbol_data_exists(data): + abort(400, message="Symbol missing") symbol = data['symbol'] @@ -36,7 +37,7 @@ def add_symbol(data): return make_response(new_symbol.as_dict(), 200, "Successfully added symbol") else: - return make_response({}, 500, "Symbol already exist for this user") + abort(500, "Symbol already exist for this user") @shares_blueprint.route('/share', methods=['DELETE']) @@ -47,14 +48,15 @@ def add_symbol(data): def remove_symbol(data): email = get_email_or_abort_401() - check_if_symbol_data_exists(data) + if not check_if_symbol_data_exists(data): + abort(400, message="Symbol missing") symbol = data['symbol'] check_share = db.session.query(Share).filter_by(symbol=symbol, email=email).first() if check_share is None: - return make_response({}, 500, "Symbol doesn't exist for this user") + abort(500, "Symbol doesn't exist for this user") else: db.session.query(Share).filter_by(symbol=symbol, email=email).delete() db.session.commit() @@ -81,7 +83,9 @@ def get_symbol(): def check_if_symbol_data_exists(data): if "symbol" not in data: - abort(400, message="Symbol missing") + return False if data['symbol'] == "" or data['symbol'] is None: - abort(400, message="Symbol missing") + return False + + return True diff --git a/api/api_blueprint_telegram.py b/api/app/blueprints/telegram.py similarity index 73% rename from api/api_blueprint_telegram.py rename to api/app/blueprints/telegram.py index 1aa6f6e..5208d71 100644 --- a/api/api_blueprint_telegram.py +++ b/api/app/blueprints/telegram.py @@ -2,11 +2,11 @@ import os from apiflask import APIBlueprint, abort -from db import db -from helper_functions import make_response, get_email_or_abort_401 -from auth import auth -from schema import TelegramIdSchema, UsersSchema -from models import User +from app.db import database as db +from app.helper_functions import make_response, get_email_or_abort_401 +from app.auth import auth +from app.schema import TelegramIdSchema, UsersSchema +from app.models import User telegram_blueprint = APIBlueprint('telegram', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -20,15 +20,11 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file def add_keyword(data): email = get_email_or_abort_401() - check_if_telegram_user_id_data_exists(data) + if not check_if_telegram_user_id_data_exists(data): + abort(400, message="User ID missing") query_user = db.session.query(User).filter_by(email=email).first() - - if query_user is None: # Username doesn't exist - abort(500, message="Unable to login") - query_user.telegram_user_id = data['telegram_user_id'] - db.session.commit() return make_response(query_user.as_dict(), 200, "Successfully connected telegram user") @@ -36,7 +32,9 @@ def add_keyword(data): def check_if_telegram_user_id_data_exists(data): if "telegram_user_id" not in data: - abort(400, message="User ID missing") + return False if data['telegram_user_id'] == "" or data['telegram_user_id'] is None: - abort(400, message="User ID missing") + return False + + return True diff --git a/api/api_blueprint_transactions.py b/api/app/blueprints/transactions.py similarity index 67% rename from api/api_blueprint_transactions.py rename to api/app/blueprints/transactions.py index 054a032..33eae41 100644 --- a/api/api_blueprint_transactions.py +++ b/api/app/blueprints/transactions.py @@ -3,11 +3,11 @@ import datetime from apiflask import abort, APIBlueprint -from db import db -from helper_functions import make_response, get_email_or_abort_401 -from models import Transaction -from schema import TransactionSchema, TransactionResponseSchema -from auth import auth +from app.db import database as db +from app.helper_functions import make_response, get_email_or_abort_401 +from app.models import Transaction +from app.schema import TransactionSchema, TransactionResponseSchema +from app.auth import auth transaction_blueprint = APIBlueprint('transaction', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -21,7 +21,17 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file def add_transaction(data): email = get_email_or_abort_401() - check_if_transaction_data_exists(data) + if not check_if_symbol_data_exists(data): + abort(400, "Symbol missing") + + if not check_if_time_data_exists(data): + abort(400, "Time missing") + + if not check_if_count_data_exists(data): + abort(400, "Count missing") + + if not check_if_price_data_exists(data): + abort(400, "Price missing") new_transaction = Transaction( email=email, @@ -53,27 +63,41 @@ def get_transaction(): return make_response(return_transactions, 200, "Successfully loaded transactions") -def check_if_transaction_data_exists(data): +def check_if_symbol_data_exists(data): if "symbol" not in data: - abort(400, message="Symbol missing") + return False if data['symbol'] == "" or data['symbol'] is None: - abort(400, message="Symbol missing") + return False + return True + + +def check_if_time_data_exists(data): if "time" not in data: - abort(400, message="Time missing") + return False if data['time'] == "" or data['time'] is None: - abort(400, message="Time missing") + return False + return True + + +def check_if_count_data_exists(data): if "count" not in data: - abort(400, message="Count missing") + return False if data['count'] == "" or data['count'] is None: - abort(400, message="Count missing") + return False + return True + + +def check_if_price_data_exists(data): if "price" not in data: - abort(400, message="Price missing") + return False if data['price'] == "" or data['price'] is None: - abort(400, message="Price missing") + return False + + return True diff --git a/api/api_blueprint_user.py b/api/app/blueprints/user.py similarity index 75% rename from api/api_blueprint_user.py rename to api/app/blueprints/user.py index ad2ec2b..771bd60 100644 --- a/api/api_blueprint_user.py +++ b/api/app/blueprints/user.py @@ -1,14 +1,15 @@ import datetime import os +from flask import current_app import jwt from apiflask import APIBlueprint, abort -from db import db -from helper_functions import check_password, hash_password, abort_if_no_admin, make_response, get_email_or_abort_401 -from models import User -from schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema, RegisterDataSchema, UpdateUserDataSchema -from auth import auth +from app.db import database as db +from app.helper_functions import check_password, hash_password, abort_if_no_admin, make_response, get_email_or_abort_401 +from app.models import User +from app.schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema, RegisterDataSchema, UpdateUserDataSchema +from app.auth import auth users_blueprint = APIBlueprint('users', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -45,8 +46,11 @@ def user(): @users_blueprint.input(schema=LoginDataSchema) @users_blueprint.doc(summary="Login", description="Returns jwt token if username and password match, otherwise returns error") def login(data): - check_if_email_data_exists(data) - check_if_password_data_exists(data) + if not check_if_password_data_exists(data): + abort(400, "Password missing") + + if not check_if_email_data_exists(data): + abort(400, "Email missing") email = data['email'] password = data['password'] @@ -59,10 +63,10 @@ def login(data): if not check_password(query_user.password, password.encode("utf-8")): # Password incorrect abort(500, message="Unable to login") - if query_user.email == os.getenv("BOT_EMAIL"): - token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=365)}, os.getenv('SECRET_KEY'), "HS256") + if query_user.email == current_app.config['BOT_EMAIL']: + token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=365)}, current_app.config['SECRET_KEY'], "HS256") else: - token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1)}, os.getenv('SECRET_KEY'), "HS256") + token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1)}, current_app.config['SECRET_KEY'], "HS256") return make_response({"token": token}, 200, "Successfully logged in") @@ -72,9 +76,14 @@ def login(data): @users_blueprint.input(schema=RegisterDataSchema) @users_blueprint.doc(summary="Register", description="Registers user") def register(data): - check_if_email_data_exists(data) - check_if_username_data_exists(data) - check_if_password_data_exists(data) + if not check_if_email_data_exists(data): + abort(400, "Email missing") + + if not check_if_username_data_exists(data): + abort(400, "Username missing") + + if not check_if_password_data_exists(data): + abort(400, "Password missing") email = data['email'] username = data['username'] @@ -107,12 +116,10 @@ def update_user(data): query_user = db.session.query(User).filter_by(email=email).first() - if query_user is None: # Username doesn't exist - abort(500, message="Unable to login") - - if "password" in data and data['password'] is not None: + if check_if_password_data_exists(data): query_user.password = hash_password(data['password']) - if "username" in data and data['username'] is not None: + + if check_if_username_data_exists(data): query_user.username = data['username'] db.session.commit() @@ -128,8 +135,11 @@ def update_user(data): def set_admin(data): abort_if_no_admin() # Only admin users can do this - check_if_email_data_exists(data) - check_if_admin_data_exists(data) + if not check_if_email_data_exists(data): + abort(400, "Email missing") + + if not check_if_admin_data_exists(data): + abort(400, "Admin data missing") email = data['email'] admin = data['admin'] @@ -137,7 +147,7 @@ def set_admin(data): query_user = db.session.query(User).filter_by(email=email).first() if query_user is None: # Username doesn't exist - abort(500, message="Unable to login") + abort(500, message="Unable to update user") query_user.admin = admin db.session.commit() @@ -151,7 +161,8 @@ def set_admin(data): @users_blueprint.auth_required(auth) @users_blueprint.doc(summary="Delete user", description="Deletes user by username") def delete_user(data): - check_if_email_data_exists(data) + if not check_if_email_data_exists(data): + abort(400, "Email missing") email = data['email'] @@ -169,31 +180,39 @@ def delete_user(data): def check_if_email_data_exists(data): if "email" not in data: - abort(400, message="Email missing") + return False if data['email'] == "" or data['email'] is None: - abort(400, message="Email missing") + return False + + return True def check_if_password_data_exists(data): if "password" not in data: - abort(400, message="Password missing") + return False if data['password'] == "" or data['password'] is None: - abort(400, message="Password missing") + return False + + return True def check_if_username_data_exists(data): if "username" not in data: - abort(400, message="Username missing") + return False if data['username'] == "" or data['username'] is None: - abort(400, message="Username missing") + return False + + return True def check_if_admin_data_exists(data): if "admin" not in data: - abort(400, message="Admin state missing") + return False if data['admin'] == "" or data['admin'] is None: - abort(400, message="Admin state missing") + return False + + return True diff --git a/api/app/config/flask.cfg b/api/app/config/flask.cfg new file mode 100644 index 0000000..1a6347c --- /dev/null +++ b/api/app/config/flask.cfg @@ -0,0 +1,26 @@ +import os +from app.schema import BaseResponseSchema + +# Flask settings +SECRET_KEY = os.getenv('SECRET_KEY', 'secret string') + + +# Flask-SQLAlchemy settings +SQLALCHEMY_DATABASE_URI = "mysql+pymysql://" + os.getenv('MYSQL_USER') + ":" + os.getenv('MYSQL_PASSWORD') + "@" + os.getenv('MYSQL_HOST') + ":" + os.getenv('MYSQL_PORT', '3306') + "/" + os.getenv('MYSQL_NAME', 'aktienbot') +SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids SQLAlchemy warning +SQLALCHEMY_ENGINE_OPTIONS = { + 'pool_size': 100, + 'pool_recycle': 240 # 4 minutes +} + +# openapi/Swagger config +BASE_RESPONSE_DATA_KEY = "data" +BASE_RESPONSE_SCHEMA = BaseResponseSchema + +BOT_EMAIL = os.getenv('BOT_EMAIL') +BOT_USERNAME = os.getenv('BOT_USERNAME') +BOT_PASSWORD = os.getenv('BOT_PASSWORD') + +ADMIN_EMAIL = os.getenv('ADMIN_EMAIL') +ADMIN_USERNAME = os.getenv('ADMIN_USERNAME') +ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD') diff --git a/api/app/config/flask_test.cfg b/api/app/config/flask_test.cfg new file mode 100644 index 0000000..01ebea6 --- /dev/null +++ b/api/app/config/flask_test.cfg @@ -0,0 +1,26 @@ +import os +from app.schema import BaseResponseSchema + +# Flask settings +SECRET_KEY = os.getenv('SECRET_KEY', 'secret string') + + +# Flask-SQLAlchemy settings +SQLALCHEMY_DATABASE_URI = "mysql+pymysql://" + os.getenv('MYSQL_USER') + ":" + os.getenv('MYSQL_PASSWORD') + "@" + os.getenv('MYSQL_HOST') + ":" + os.getenv('MYSQL_PORT', '3306') + "/" + os.getenv('MYSQL_NAME', 'aktienbot_test') +SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids SQLAlchemy warning +SQLALCHEMY_ENGINE_OPTIONS = { + 'pool_size': 100, + 'pool_recycle': 240 # 4 minutes +} + +# openapi/Swagger config +BASE_RESPONSE_DATA_KEY = "data" +BASE_RESPONSE_SCHEMA = BaseResponseSchema + +BOT_EMAIL = os.getenv('BOT_EMAIL') +BOT_USERNAME = os.getenv('BOT_USERNAME') +BOT_PASSWORD = os.getenv('BOT_PASSWORD') + +ADMIN_EMAIL = os.getenv('ADMIN_EMAIL') +ADMIN_USERNAME = os.getenv('ADMIN_USERNAME') +ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD') diff --git a/api/db.py b/api/app/db.py similarity index 63% rename from api/db.py rename to api/app/db.py index f0b13d6..890af1d 100644 --- a/api/db.py +++ b/api/app/db.py @@ -1,3 +1,3 @@ from flask_sqlalchemy import SQLAlchemy -db = SQLAlchemy() +database = SQLAlchemy() diff --git a/api/helper_functions.py b/api/app/helper_functions.py similarity index 72% rename from api/helper_functions.py rename to api/app/helper_functions.py index 4ebf10e..516d0e7 100644 --- a/api/helper_functions.py +++ b/api/app/helper_functions.py @@ -1,12 +1,12 @@ -import os +from flask import current_app import bcrypt import jwt from apiflask import abort from flask import request, jsonify -from db import db -from models import User +from app.db import database as db +from app.models import User def hash_password(password): @@ -17,27 +17,14 @@ def check_password(hashed_password, user_password): return bcrypt.checkpw(user_password, hashed_password) -def get_token(): - token = None - if 'Authorization' in request.headers: - token = request.headers['Authorization'].split(" ")[1] - - return token - - -def extract_token_data(token): - if token is not None: - try: - return jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"]) - except jwt.PyJWTError: - return None - else: - return None - - def get_email_from_token_data(): if 'Authorization' in request.headers: - token = request.headers['Authorization'].split(" ")[1] + token = request.headers['Authorization'].split(" ") + + if len(token) < 2: + return None + else: + token = token[1] if token is not None: if ':' in token: # Maybe bot token, check if token valid and return username after ":" then @@ -45,7 +32,7 @@ def get_email_from_token_data(): token = token.split(":")[0] try: - if jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['email'] == os.getenv("BOT_EMAIL"): + if jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] == current_app.config['BOT_EMAIL']: res = db.session.query(User).filter_by(telegram_user_id=telegram_user_id).first() if res is not None: @@ -59,7 +46,7 @@ def get_email_from_token_data(): else: # "Normal" token, extract username from token try: - return jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])['email'] + return jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] except jwt.PyJWTError: return None diff --git a/api/models.py b/api/app/models.py similarity index 98% rename from api/models.py rename to api/app/models.py index d85e49c..6c06c7b 100644 --- a/api/models.py +++ b/api/app/models.py @@ -1,4 +1,4 @@ -from db import db +from app.db import database as db class User(db.Model): diff --git a/api/schema.py b/api/app/schema.py similarity index 100% rename from api/schema.py rename to api/app/schema.py diff --git a/api/config.py b/api/config.py deleted file mode 100644 index a12c60a..0000000 --- a/api/config.py +++ /dev/null @@ -1,55 +0,0 @@ -import os - -from dotenv import load_dotenv - -from schema import BaseResponseSchema - -load_dotenv() - - -class ConfigClass(object): - """ Flask application config """ - - # Flask settings - SECRET_KEY = os.getenv('SECRET_KEY') - - # Flask-SQLAlchemy settings - SQLALCHEMY_DATABASE_URI = "mysql+pymysql://" + \ - os.getenv('MYSQL_USER') + ":" + \ - os.getenv('MYSQL_PASSWORD') + "@" + \ - os.getenv('MYSQL_HOST') + ":" + \ - (os.getenv("MYSQL_PORT") or str(3306)) + "/" + \ - os.getenv('MYSQL_DATABASE') - SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids SQLAlchemy warning - SQLALCHEMY_ENGINE_OPTIONS = { - 'pool_size': 100, - 'pool_recycle': 240 # 4 minutes - } - - # openapi/Swagger config - SERVERS = [ - { - "name": "Production", - "url": "https://aktienbot.flokaiser.com" - }, - { - "name": "Local", - "url": "http://127.0.0.1:5000" - } - ] - INFO = { - 'description': 'Webengineering 2 | Telegram Aktienbot', - 'version': '0.0.1' - # 'termsOfService': 'http://example.com', - # 'contact': { - # 'name': 'API Support', - # 'url': 'http://www.example.com/support', - # 'email': 'support@example.com' - # }, - # 'license': { - # 'name': 'Apache 2.0', - # 'url': 'http://www.apache.org/licenses/LICENSE-2.0.html' - # } - } - BASE_RESPONSE_DATA_KEY = "data" - BASE_RESPONSE_SCHEMA = BaseResponseSchema diff --git a/api/requirements.txt b/api/requirements.txt index 0ab141c..16da00c 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -7,4 +7,6 @@ pymysql==1.0.2 pyjwt==2.3.0 apiflask==0.12.0 flask-cors==3.0.10 -bcrypt==3.2.0 \ No newline at end of file +bcrypt==3.2.0 +pytest +pytest-cov \ No newline at end of file diff --git a/api/tests/conftest.py b/api/tests/conftest.py new file mode 100644 index 0000000..c772474 --- /dev/null +++ b/api/tests/conftest.py @@ -0,0 +1,108 @@ +import json +import os + +import pytest +from app import create_app, db +from app.models import User, Transaction, Keyword, Share + +from app.helper_functions import hash_password + + +@pytest.fixture(scope='module') +def new_user(): + user = User( + email="user@example.com", + username="user", + password=hash_password("password"), + admin=False + ) + return user + + +@pytest.fixture(scope='module') +def new_transaction(): + transaction = Transaction( + email="user@example.com", + symbol="DTEGY", + time="2022-03-29T10:00:00.000Z", + count=10, + price=9.99 + ) + return transaction + + +@pytest.fixture(scope='module') +def new_keyword(): + keyword = Keyword( + email="user@example.com", + keyword="Elon Musk", + ) + return keyword + + +@pytest.fixture(scope='module') +def new_share(): + share = Share( + email="user@example.com", + symbol="DTEGY", + ) + return share + + +@pytest.fixture(scope='module') +def test_client(): + flask_app = create_app('config/flask_test.cfg') + + # Create a test client using the Flask application configured for testing + with flask_app.test_client() as testing_client: + # Establish an application context + with flask_app.app_context(): + yield testing_client # this is where the testing happens! + + +@pytest.fixture(scope='function') +def init_database(test_client): + # Create the database and the database table + db.create_all() + + # Insert user data + user1 = User( + email="user1@example.com", + username="user1", + password=hash_password("password"), + telegram_user_id="12345678", + admin=False + ) + user2 = User( + email="user2@example.com", + username="user2", + password=hash_password("password"), + telegram_user_id="87654321", + admin=False + ) + admin = User( + email="admin1@example.com", + username="admin1", + password=hash_password("admin1"), + telegram_user_id="00000000", + admin=True + ) + bot = User( + email="bot1@example.com", + username="bot1", + password=hash_password("bot1"), + telegram_user_id="00000000", + admin=False + ) + db.session.add(user1) + db.session.add(user2) + db.session.add(admin) + db.session.add(bot) + + # Commit the changes for the users + db.session.commit() + + yield # this is where the testing happens! + + db.session.commit() + db.drop_all() diff --git a/api/tests/functional/__init__.py b/api/tests/functional/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/tests/functional/test_keyword.py b/api/tests/functional/test_keyword.py new file mode 100644 index 0000000..72763ca --- /dev/null +++ b/api/tests/functional/test_keyword.py @@ -0,0 +1,191 @@ +""" +This file (test_keyword.py) contains the functional tests for the `keyword` blueprint. +""" +import json + + +def test_add_keyword_not_logged_in(test_client, init_database): + """ + Test POST '/api/keyword' + + User is not logged in + """ + response = test_client.post('/api/keyword') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_add_keyword_user1_logged_in(test_client, init_database): + """ + Test POST '/api/keyword' + + User1 is logged in + """ + response = test_client.post('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully added keyword' in response.data + + +def test_add_keyword_user1_logged_in_but_keyword_exist(test_client, init_database): + """ + Test POST '/api/keyword' + + User1 is logged in + Add keyword two times + """ + test_client.post('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.post('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 500 + assert b'Keyword already exist for this user' in response.data + + +def test_add_keyword_user1_logged_in_but_keyword_missing(test_client, init_database): + """ + Test POST '/api/keyword' + + User1 is logged in + Keyword is missing in post data + """ + response = test_client.post('/api/keyword', data=json.dumps(dict()), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_add_keyword_user1_logged_in_but_keyword_empty(test_client, init_database): + """ + Test POST '/api/keyword' + + User1 is logged in + Keyword is empty in post data + """ + response = test_client.post('/api/keyword', data=json.dumps(dict(keyword="")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_get_keyword_not_logged_in(test_client, init_database): + """ + Test GET '/api/keyword' + + User is not logged in + """ + response = test_client.get('/api/keywords') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_get_keyword_user1_logged_in_empty_response(test_client, init_database): + """ + Test GET '/api/keyword' + + User1 is logged in + Empty response + """ + response = test_client.get('/api/keywords', headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'"data":[' in response.data + + +def test_get_keyword_user1_logged_in_response_data(test_client, init_database): + """ + Test GET '/api/keyword' + + User1 is logged in + Create some keywords for user1 + """ + test_client.post('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.get('/api/keywords', headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'"data":[' in response.data + + +def test_delete_keyword_not_logged_in(test_client, init_database): + """ + Test DELETE '/api/keyword' + + User is not logged in + """ + response = test_client.delete('/api/keyword') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_delete_keyword_user1_logged_in_but_empty_keyword(test_client, init_database): + """ + Test DELETE '/api/keyword' + + User1 is logged in + Keyword empty in in delete data + """ + response = test_client.delete('/api/keyword', data=json.dumps(dict(keyword="")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_delete_keyword_user1_logged_in_but_missing_keyword(test_client, init_database): + """ + Test DELETE '/api/keyword' + + User1 is logged in + Keyword missing in in delete data + """ + response = test_client.delete('/api/keyword', data=json.dumps(dict()), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_delete_keyword_user1_logged_in_keyword_exists(test_client, init_database): + """ + Test DELETE '/api/keyword' + + User1 is logged in + Keyword exists + """ + test_client.post('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.delete('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully removed keyword' in response.data + + +def test_delete_keyword_user1_logged_in_keyword_not_exists(test_client, init_database): + """ + Test DELETE '/api/keyword' + + User1 is logged in + Keyword doesn't exists + """ + response = test_client.delete('/api/keyword', data=json.dumps(dict(keyword="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 500 + assert b'Keyword doesn\'t exist for this user' in response.data + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" diff --git a/api/tests/functional/test_portfolio.py b/api/tests/functional/test_portfolio.py new file mode 100644 index 0000000..10fd8cc --- /dev/null +++ b/api/tests/functional/test_portfolio.py @@ -0,0 +1,59 @@ +""" +This file (test_portfolio.py) contains the functional tests for the `portfolio` blueprint. +""" +import json + + +def test_get_portfolio_not_logged_in_empty_response(test_client, init_database): + """ + Test GET '/api/portfolio' + + User is not logged in + """ + response = test_client.get('/api/portfolio') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_get_portfolio_user1_logged_in_empty_response(test_client, init_database): + """ + Test GET '/api/portfolio' + + User1 is logged in + Empty response + """ + response = test_client.get('/api/portfolio', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'"data":[]' in response.data + assert b'Successfully loaded symbols' in response.data + + +def test_get_portfolio_user1_logged_in_response_data(test_client, init_database): + """ + Test GET '/api/portfolio' + + User1 is logged in + Create transaction data + """ + test_client.post('/api/transaction', data=json.dumps(dict(count=5, price=9.99, symbol="DTEGY", time="2022-03-29T10:00:00.000Z")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + + response = test_client.get('/api/portfolio', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'"data":[]' not in response.data + assert b'"data":[' in response.data + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" diff --git a/api/tests/functional/test_share.py b/api/tests/functional/test_share.py new file mode 100644 index 0000000..61772fa --- /dev/null +++ b/api/tests/functional/test_share.py @@ -0,0 +1,191 @@ +""" +This file (test_share.py) contains the functional tests for the `share` blueprint. +""" +import json + + +def test_add_share_not_logged_in(test_client, init_database): + """ + Test POST '/api/share' + + User is not logged in + """ + response = test_client.post('/api/share') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_add_share_user1_logged_in(test_client, init_database): + """ + Test POST '/api/share' + + User1 is logged in + """ + response = test_client.post('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully added symbol' in response.data + + +def test_add_share_user1_logged_in_but_symbol_exist(test_client, init_database): + """ + Test POST '/api/share' + + User1 is logged in + Add symbol two times + """ + test_client.post('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.post('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 500 + assert b'Symbol already exist for this user' in response.data + + +def test_add_share_user1_logged_in_but_symbol_missing(test_client, init_database): + """ + Test POST '/api/share' + + User1 is logged in + Symbol is missing in post data + """ + response = test_client.post('/api/share', data=json.dumps(dict()), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_add_share_user1_logged_in_but_symbol_empty(test_client, init_database): + """ + Test POST '/api/share' + + User1 is logged in + Symbol is empty in post data + """ + response = test_client.post('/api/share', data=json.dumps(dict(symbol="")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_get_share_not_logged_in(test_client, init_database): + """ + Test GET '/api/share' + + User is not logged in + """ + response = test_client.get('/api/shares') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_get_share_user1_logged_in_empty_response(test_client, init_database): + """ + Test GET '/api/share' + + User1 is logged in + Empty response + """ + response = test_client.get('/api/shares', headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'"data":[' in response.data + + +def test_get_share_user1_logged_in_response_data(test_client, init_database): + """ + Test GET '/api/share' + + User1 is logged in + Create some symbols for user1 + """ + test_client.post('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.get('/api/shares', headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'"data":[' in response.data + + +def test_delete_share_not_logged_in(test_client, init_database): + """ + Test DELETE '/api/share' + + User is not logged in + """ + response = test_client.delete('/api/share') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_delete_share_user1_logged_in_but_empty_symbol(test_client, init_database): + """ + Test DELETE '/api/share' + + User1 is logged in + Symbol empty in in delete data + """ + response = test_client.delete('/api/share', data=json.dumps(dict(symbol="")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_delete_share_user1_logged_in_but_missing_symbol(test_client, init_database): + """ + Test DELETE '/api/share' + + User1 is logged in + Symbol missing in in delete data + """ + response = test_client.delete('/api/share', data=json.dumps(dict()), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_delete_share_user1_logged_in_symbol_exists(test_client, init_database): + """ + Test DELETE '/api/share' + + User1 is logged in + Symbol exists + """ + test_client.post('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.delete('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully removed symbol' in response.data + + +def test_delete_share_user1_logged_in_symbol_not_exists(test_client, init_database): + """ + Test DELETE '/api/share' + + User1 is logged in + Symbol doesn't exists + """ + response = test_client.delete('/api/share', data=json.dumps(dict(symbol="DTEGY")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 500 + assert b'Symbol doesn\'t exist for this user' in response.data + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" diff --git a/api/tests/functional/test_telegram.py b/api/tests/functional/test_telegram.py new file mode 100644 index 0000000..756f6cd --- /dev/null +++ b/api/tests/functional/test_telegram.py @@ -0,0 +1,66 @@ +""" +This file (test_telegram.py) contains the functional tests for the `telegram` blueprint. +""" +import json + + +def test_add_telegram_not_logged_in(test_client, init_database): + """ + Test POST '/api/telegram' + + User is not logged in + """ + response = test_client.post('/api/telegram') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_add_telegram_user1_logged_in(test_client, init_database): + """ + Test POST '/api/telegram' + + User1 is logged in + """ + response = test_client.post('/api/telegram', data=json.dumps(dict(telegram_user_id="12345678")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully connected telegram user' in response.data + + +def test_add_telegram_user1_logged_in_user_data_missing(test_client, init_database): + """ + Test POST '/api/telegram' + + User1 is logged in + telegram_user_id is missing + """ + response = test_client.post('/api/telegram', data=json.dumps(dict()), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_add_telegram_user1_logged_in_user_data_empty(test_client, init_database): + """ + Test POST '/api/telegram' + + User1 is logged in + telegram_user_id is empty + """ + response = test_client.post('/api/telegram', data=json.dumps(dict(telegram_user_id="")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" diff --git a/api/tests/functional/test_transaction.py b/api/tests/functional/test_transaction.py new file mode 100644 index 0000000..d50578a --- /dev/null +++ b/api/tests/functional/test_transaction.py @@ -0,0 +1,103 @@ +""" +This file (test_transaction.py) contains the functional tests for the `transaction` blueprint. +""" +import json + + +def test_add_transaction_not_logged_in(test_client, init_database): + """ + Test POST '/api/transaction' + + User is not logged in + """ + response = test_client.get('/api/portfolio') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_add_transaction_user1_logged_in_missing_data(test_client, init_database): + """ + Test POST '/api/transaction' + + User1 is logged in + Data missing + """ + # symbol missing + response = test_client.post('/api/transaction', data=json.dumps(dict(time="2022-03-29T10:00:00.000Z", count=10, price=9.99)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + # time missing + response = test_client.post('/api/transaction', data=json.dumps(dict(symbol="DTEGY", count=10, price=9.99)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + # count missing + response = test_client.post('/api/transaction', data=json.dumps(dict(symbol="DTEGY", time="2022-03-29T10:00:00.000Z", price=9.99)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + # price missing + response = test_client.post('/api/transaction', data=json.dumps(dict(symbol="DTEGY", time="2022-03-29T10:00:00.000Z", count=10)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_get_transaction_not_logged_in(test_client, init_database): + """ + Test GET '/api/transaction' + + User is not logged in + """ + response = test_client.get('/api/transactions') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_get_transaction_user1_logged_in_empty_response(test_client, init_database): + """ + Test GET '/api/transaction' + + User1 is logged in + """ + response = test_client.get('/api/transactions', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully loaded transactions' in response.data + + +def test_get_transaction_user1_logged_in_response_data(test_client, init_database): + """ + Test GET '/api/transaction' + + User1 is logged in + Create transaction + """ + test_client.post('/api/transaction', data=json.dumps(dict(count=5, price=9.99, symbol="DTEGY", time="2022-03-29T10:00:00.000Z")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + + response = test_client.get('/api/transactions', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully loaded transactions' in response.data + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" diff --git a/api/tests/functional/test_user.py b/api/tests/functional/test_user.py new file mode 100644 index 0000000..6a88089 --- /dev/null +++ b/api/tests/functional/test_user.py @@ -0,0 +1,513 @@ +""" +This file (test_user.py) contains the functional tests for the `users` blueprint. +""" +import json + + +def test_login_with_valid_data(test_client, init_database): + """ + Test POST '/api/user/login' + + Valid data + """ + response = test_client.post('/api/user/login', data=json.dumps(dict(email="user1@example.com", password="password")), content_type='application/json') + assert response.status_code == 200 + assert b'Successfully logged in' in response.data + + +def test_login_with_wrong_password(test_client, init_database): + """ + Test POST '/api/user/login' + + Wrong password + """ + response = test_client.post('/api/user/login', data=json.dumps(dict(email="user2@example.com", password="password2")), content_type='application/json') + assert response.status_code == 500 + assert b'Unable to login' in response.data + + +def test_login_user_not_exist(test_client, init_database): + """ + Test POST '/api/user/login' + + User doesn't exist + """ + response = test_client.post('/api/user/login', data=json.dumps(dict(email="notexistinguser@example.com", password="password")), content_type='application/json') + assert response.status_code == 500 + assert b'Unable to login' in response.data + + +def test_login_email_missing(test_client, init_database): + """ + Test POST '/api/user/login' + + Email missing + """ + response = test_client.post('/api/user/login', data=json.dumps(dict(password="password")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_login_password_missing(test_client, init_database): + """ + Test POST '/api/user/login' + + Password missing + """ + response = test_client.post('/api/user/login', data=json.dumps(dict(email="user1@example.com")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_register_valid_data(test_client, init_database): + """ + Test POST '/api/user/register' + + Valid data + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + assert response.status_code == 200 + assert b'Successfully registered user' in response.data + + +def test_register_user_exists_already(test_client, init_database): + """ + Test POST '/api/user/register' + + User exists already + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + assert response.status_code == 500 + assert b'Email already exist' in response.data + + +def test_register_email_missing(test_client, init_database): + """ + Test POST '/api/user/register' + + Email missing + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(password="password", username="user3")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_register_email_empty(test_client, init_database): + """ + Test POST '/api/user/register' + + Email empty + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(password="password", username="user3", email="")), content_type='application/json') + assert response.status_code == 400 + assert b'Not a valid email address' in response.data + + +def test_register_password_missing(test_client, init_database): + """ + Test POST '/api/user/register' + + Password missing + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", username="user3")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_register_password_empty(test_client, init_database): + """ + Test POST '/api/user/register' + + password empty + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", username="user3", password="")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_register_username_missing(test_client, init_database): + """ + Test POST '/api/user/register' + + Username missing + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(password="password", email="user3@example.com")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_register_username_empty(test_client, init_database): + """ + Test POST '/api/user/register' + + Username empty + """ + response = test_client.post('/api/user/register', data=json.dumps(dict(password="password", username="", email="user3@example.com")), content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_delete_user_not_logged_in(test_client, init_database): + """ + Test DELETE '/api/user' + + User is not logged in + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.delete('/api/user', data=json.dumps(dict(email="user3@example.com")), content_type='application/json') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_delete_user_same_user_logged_in(test_client, init_database): + """ + Test DELETE '/api/user' + + User3 is logged in + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.delete('/api/user', + data=json.dumps(dict(email="user3@example.com")), + content_type='application/json', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user3@example.com", "password"))}) + assert response.status_code == 200 + assert b'Successfully removed user' in response.data + + +def test_delete_user_different_user_logged_in_no_admin(test_client, init_database): + """ + Test DELETE '/api/user' + + Different user is logged in -> no admin + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.delete('/api/user', + data=json.dumps(dict(email="user3@example.com")), + content_type='application/json', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 401 + assert b'Only admin users can access this' in response.data + + +def test_delete_user_different_user_logged_in_admin(test_client, init_database): + """ + Test DELETE '/api/user' + + Different user is logged in -> admin + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.delete('/api/user', + data=json.dumps(dict(email="user3@example.com")), + content_type='application/json', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}) + assert response.status_code == 200 + assert b'Successfully removed user' in response.data + + +def test_delete_user_email_missing(test_client, init_database): + """ + Test DELETE '/api/user' + + Email missing + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.delete('/api/user', + data=json.dumps(dict()), + content_type='application/json', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user3@example.com", "password"))}) + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_delete_user_email_empty(test_client, init_database): + """ + Test DELETE '/api/user' + + Email empty + """ + test_client.post('/api/user/register', data=json.dumps(dict(email="user3@example.com", password="password", username="user3")), content_type='application/json') + response = test_client.delete('/api/user', + data=json.dumps(dict(email="")), + content_type='application/json', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user3@example.com", "password"))}) + assert response.status_code == 400 + assert b'Not a valid email address' in response.data + + +def test_get_current_user_user_not_logged_in(test_client, init_database): + """ + Test GET '/api/user' + + User is not logged in + """ + response = test_client.get('/api/user') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_get_current_user_user1_logged_in(test_client, init_database): + """ + Test GET '/api/user' + + User1 is logged in + """ + response = test_client.get('/api/user', headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'user1' in response.data + assert b'user2' not in response.data + + +def test_get_current_user_bot_logged_in_user_exists(test_client, init_database): + """ + Test GET '/api/user' + + Bot1 is logged in and requests user 12345678 + """ + response = test_client.get('/api/user', headers={"Authorization": "Bearer {}".format(get_token(test_client, "bot1@example.com", "bot1") + ":12345678")}) + assert response.status_code == 200 + assert b'user1' in response.data + assert b'bot' not in response.data + + +def test_get_current_user_bot_logged_in_user_not_exists(test_client, init_database): + """ + Test GET '/api/user' + + Bot1 is logged in and requests user 1234 (not existing) + """ + response = test_client.get('/api/user', headers={"Authorization": "Bearer {}".format(get_token(test_client, "bot1@example.com", "bot1") + ":1234")}) + assert response.status_code == 401 + assert b'Unable to login' in response.data + + +def test_get_current_user_user1_logged_in_but_no_bot(test_client, init_database): + """ + Test GET '/api/user' + + User1 is logged in and requests user 1234 (not existing) + Fails because user1 is not a bot + """ + response = test_client.get('/api/user', headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password") + ":12345678")}) + assert response.status_code == 401 + assert b'Unable to login' in response.data + + +def test_get_current_user_invalid_token(test_client, init_database): + """ + Test GET '/api/user' + + Invalid Bearer token + """ + response = test_client.get('/api/user', headers={"Authorization": "Bearer {}".format("invalidtoken:12345678")}) + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_update_user_not_logged_in(test_client, init_database): + """ + Test PUT '/api/user' + + User is not logged in + """ + response = test_client.put('/api/user') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_update_user1_logged_in_password_username(test_client, init_database): + """ + Test PUT '/api/user' + + User1 is logged in + Change Username and Password + """ + test_client.put('/api/user', data=json.dumps(dict(username="user4", password="password")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + response = test_client.get('/api/user', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'user4' in response.data + + +def test_update_user1_logged_in_password(test_client, init_database): + """ + Test PUT '/api/user' + + User1 is logged in + Change Password + """ + response = test_client.put('/api/user', data=json.dumps(dict(password="password123")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'Successfully updated user' in response.data + + +def test_update_user1_logged_in_username(test_client, init_database): + """ + Test PUT '/api/user' + + User1 is logged in + Change Username + """ + response = test_client.put('/api/user', data=json.dumps(dict(username="user1")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}) + assert response.status_code == 200 + assert b'Successfully updated user' in response.data + + +def test_set_admin_user_not_logged_in(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + User is not logged in + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="user1@example.com", admin=True)), content_type='application/json') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_set_admin_user1_logged_in(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + User1 is logged in (no admin) + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="user1@example.com", admin=True)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 401 + assert b'Only admin users can access this' in response.data + + +def test_set_admin_admin1_logged_in(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + Admin1 is logged in (admin) + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="user1@example.com", admin=True)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully updated users admin rights' in response.data + + +def test_set_admin_admin1_logged_in_user_not_exist(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + Admin1 is logged in (admin) + notexistinguser@example.com does not exist + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="notexistinguser@example.com", admin=True)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 500 + assert b'Unable to update user' in response.data + + +def test_set_admin_admin1_logged_in_email_missing(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + Admin1 is logged in (admin) + email missing + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(admin=True)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_set_admin_admin1_logged_in_email_empty(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + Admin1 is logged in (admin) + email missing + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="", admin=True)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'Not a valid email address.' in response.data + + +def test_set_admin_admin1_logged_in_admin_missing(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + Admin1 is logged in (admin) + admin data missing + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="user1@example.com")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'missing' in response.data + + +def test_set_admin_admin1_logged_in_admin_empty(test_client, init_database): + """ + Test PUT '/api/user/setAdmin' + + Admin1 is logged in (admin) + admin data missing + """ + response = test_client.put('/api/user/setAdmin', data=json.dumps(dict(email="user1@example.com", admin=None)), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 400 + assert b'Field may not be null' in response.data + + +def test_get_users_not_logged_in(test_client, init_database): + """ + Test GET '/api/users' + + User is not logged in + """ + response = test_client.get('/api/users') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_get_users_user1_logged_in(test_client, init_database): + """ + Test GET '/api/users' + + User1 is logged in (not admin) + """ + response = test_client.get('/api/users', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + + assert response.status_code == 401 + assert b'Only admin users can access this' in response.data + + +def test_get_users_admin1_logged_in(test_client, init_database): + """ + Test GET '/api/users' + + Admin1 is logged in (admin) + """ + response = test_client.get('/api/users', + headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully received all users' in response.data + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" diff --git a/api/tests/pytest.ini b/api/tests/pytest.ini new file mode 100644 index 0000000..e69de29 diff --git a/api/tests/unit/__init__.py b/api/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/tests/unit/test_auth.py b/api/tests/unit/test_auth.py new file mode 100644 index 0000000..cc996cd --- /dev/null +++ b/api/tests/unit/test_auth.py @@ -0,0 +1,14 @@ +""" +This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. +""" +import datetime + +import jwt +from app.auth import * + + +def test_verify_token(): + """ + Test verify_token function + """ + assert verify_token(None) is False diff --git a/api/tests/unit/test_helper_functions.py b/api/tests/unit/test_helper_functions.py new file mode 100644 index 0000000..e5b4b2c --- /dev/null +++ b/api/tests/unit/test_helper_functions.py @@ -0,0 +1,67 @@ +""" +This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. +""" +import datetime + +import jwt +from app.helper_functions import * + + +def test_hash_password(): + """ + Test hash_password function + """ + assert hash_password("password") != "password" + + +def test_check_password(): + """ + Test check_password function + """ + hashed = hash_password("password") + assert check_password(hashed, "password".encode("utf-8")) is True + assert check_password(hashed, "password1".encode("utf-8")) is False + + +def test_get_email_from_token_data(test_client, init_database): + """ + Test get_email_from_token_data function + """ + + # Invalid token + # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer XXXXX'}) + # request_ctx.push() + # assert get_email_from_token_data("SECRET_KEY") is None + # + # # empty token + # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer'}) + # request_ctx.push() + # assert get_email_from_token_data("SECRET_KEY") is None + # + # # valid token + # exp = datetime.datetime.utcnow() + datetime.timedelta(days=365) + # token = jwt.encode({'email': "user@example.com", 'exp': exp}, "SECRET_KEY", "HS256") + # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token}) + # request_ctx.push() + # assert get_email_from_token_data("SECRET_KEY") == "user@example.com" + # + # # expired token + # exp = datetime.datetime.utcnow() + datetime.timedelta(days=-1) + # token = jwt.encode({'email': "user@example.com", 'exp': exp}, "SECRET_KEY", "HS256") + # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token}) + # request_ctx.push() + # assert get_email_from_token_data("SECRET_KEY") is None + # + # # bot token (includes ":") but user doesn't exist + # exp = datetime.datetime.utcnow() + datetime.timedelta(days=365) + # token = jwt.encode({'email': "bot@example.com", 'exp': exp}, "SECRET_KEY", "HS256") + # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token + ':12345'}) + # request_ctx.push() + # assert get_email_from_token_data("SECRET_KEY") is None + + # # bot token (includes ":") user exists + # exp = datetime.datetime.utcnow() + datetime.timedelta(days=365) + # token = jwt.encode({'email': "bot@example.com", 'exp': exp}, "SECRET_KEY", "HS256") + # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token + ':12345'}) + # request_ctx.push() + # assert get_email_from_token_data("SECRET_KEY") is None \ No newline at end of file diff --git a/api/tests/unit/test_models.py b/api/tests/unit/test_models.py new file mode 100644 index 0000000..e8a336a --- /dev/null +++ b/api/tests/unit/test_models.py @@ -0,0 +1,116 @@ +""" +This file (test_models.py) contains the unit tests for the models.py file. +""" +from app.models import User, Transaction, Keyword, Share + +from app.helper_functions import hash_password + + +def test_new_user(): + """ + GIVEN a User model + WHEN a new User is created + THEN check the email, password, username, telegram_user_id and admin fields are defined correctly + """ + user = User( + email="user@example.com", + username="user", + password=hash_password("password"), + admin=False + ) + assert user.email == 'user@example.com' + assert user.password != 'password' + assert user.username == "user" + assert user.telegram_user_id is None + assert user.admin is False + assert user.as_dict() == {'email': 'user@example.com', 'username': 'user', 'telegram_user_id': None, 'admin': False} + + +def test_new_user_with_fixture(new_user): + """ + GIVEN a User model + WHEN a new User is created + THEN check the email and password_hashed fields are defined correctly + """ + assert new_user.email == 'user@example.com' + assert new_user.password != 'password' + + +def test_new_transaction(): + """ + GIVEN a Transaction model + WHEN a new Transaction is created + THEN check the email, symbol, time count and price fields are defined correctly + """ + transaction = Transaction( + email="user@example.com", + symbol="DTEGY", + time="2022-03-29T10:00:00.000Z", + count=10, + price=9.99 + ) + assert transaction.email == 'user@example.com' + assert transaction.symbol == "DTEGY" + assert transaction.count == 10 + assert transaction.price == 9.99 + assert transaction.as_dict() == {'t_id': None, 'email': 'user@example.com', 'symbol': 'DTEGY', 'time': '2022-03-29T10:00:00.000Z', 'count': 10, 'price': 9.99} + + +def test_new_transaction_with_fixture(new_transaction): + """ + GIVEN a User model + WHEN a new User is created + THEN check the email and password_hashed fields are defined correctly + """ + assert new_transaction.email == 'user@example.com' + assert new_transaction.symbol == 'DTEGY' + + +def test_new_keyword(): + """ + GIVEN a Transaction model + WHEN a new Transaction is created + THEN check the email, symbol, time count and price fields are defined correctly + """ + keyword = Keyword( + email="user@example.com", + keyword="Elon Musk" + ) + assert keyword.email == 'user@example.com' + assert keyword.keyword == "Elon Musk" + assert keyword.as_dict() == {'s_id': None, 'email': 'user@example.com', 'keyword': 'Elon Musk'} + + +def test_new_keyword_with_fixture(new_keyword): + """ + GIVEN a User model + WHEN a new User is created + THEN check the email and password_hashed fields are defined correctly + """ + assert new_keyword.email == 'user@example.com' + assert new_keyword.keyword == 'Elon Musk' + + +def test_new_share(): + """ + GIVEN a Transaction model + WHEN a new Transaction is created + THEN check the email, symbol, time count and price fields are defined correctly + """ + share = Share( + email="user@example.com", + symbol="DTEGY" + ) + assert share.email == 'user@example.com' + assert share.symbol == "DTEGY" + assert share.as_dict() == {'a_id': None, 'email': 'user@example.com', 'symbol': 'DTEGY'} + + +def test_new_share_with_fixture(new_share): + """ + GIVEN a User model + WHEN a new User is created + THEN check the email and password_hashed fields are defined correctly + """ + assert new_share.email == 'user@example.com' + assert new_share.symbol == 'DTEGY' diff --git a/api/tests/unit/test_transaction.py b/api/tests/unit/test_transaction.py new file mode 100644 index 0000000..66f51ae --- /dev/null +++ b/api/tests/unit/test_transaction.py @@ -0,0 +1,40 @@ +""" +This file (test_transaction.py) contains the unit tests for the blueprints/transaction.py file. +""" +from app.blueprints.transactions import * + + +def test_check_if_symbol_data_exists(): + """ + Test check_if_symbol_data_exists function + """ + assert check_if_symbol_data_exists(dict(symbol='DTEGY')) is True + assert check_if_symbol_data_exists(dict(symbol='')) is False + assert check_if_symbol_data_exists(dict()) is False + + +def test_check_if_time_data_exists(): + """ + Test check_if_time_data_exists function + """ + assert check_if_time_data_exists(dict(time='2022-03-29T10:00:00.000Z')) is True + assert check_if_time_data_exists(dict(time='')) is False + assert check_if_time_data_exists(dict()) is False + + +def test_check_if_count_data_exists(): + """ + Test check_if_count_data_exists function + """ + assert check_if_count_data_exists(dict(count=10)) is True + assert check_if_count_data_exists(dict(count=None)) is False + assert check_if_count_data_exists(dict()) is False + + +def test_check_if_price_data_exists(): + """ + Test check_if_price_data_exists function + """ + assert check_if_price_data_exists(dict(price=9.99)) is True + assert check_if_price_data_exists(dict(price=None)) is False + assert check_if_price_data_exists(dict()) is False diff --git a/api/tests/unit/test_user.py b/api/tests/unit/test_user.py new file mode 100644 index 0000000..91ad580 --- /dev/null +++ b/api/tests/unit/test_user.py @@ -0,0 +1,40 @@ +""" +This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. +""" +from app.blueprints.user import * + + +def test_check_if_email_data_exists(): + """ + Test check_if_email_data_exists function + """ + assert check_if_email_data_exists(dict(email='user@example.com')) is True + assert check_if_email_data_exists(dict(email='')) is False + assert check_if_email_data_exists(dict()) is False + + +def test_check_if_password_data_exists(): + """ + Test check_if_password_data_exists function + """ + assert check_if_password_data_exists(dict(password='password')) is True + assert check_if_password_data_exists(dict(password='')) is False + assert check_if_password_data_exists(dict()) is False + + +def test_check_if_username_data_exists(): + """ + Test check_if_username_data_exists function + """ + assert check_if_username_data_exists(dict(username='user')) is True + assert check_if_username_data_exists(dict(username='')) is False + assert check_if_username_data_exists(dict()) is False + + +def test_check_if_admin_data_exists(): + """ + Test check_if_admin_data_exists function + """ + assert check_if_admin_data_exists(dict(admin=True)) is True + assert check_if_admin_data_exists(dict(admin=None)) is False + assert check_if_admin_data_exists(dict()) is False From 4b7dfa8afd679ad1218f01b58711965b22a8073a Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Wed, 30 Mar 2022 10:51:38 +0200 Subject: [PATCH 106/263] Update README.md --- api/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/api/README.md b/api/README.md index e771473..e9daaa2 100644 --- a/api/README.md +++ b/api/README.md @@ -10,6 +10,16 @@ Aktienbot API 2. Or set variables using `export` or `set` commands. (Windows `set`, Linux `export`) 4. Run api `python api/app.py` + +## Testing +1. Create virtual environment `python -m venv venv env/Scripts/activate` +2. Install requirements `pip install -r api/requirements.txt` +3. Set environment variables (see list below) + 1. Use `.env`-file in `api` directory like `.env.example` + 2. Or set variables using `export` or `set` commands. (Windows `set`, Linux `export`) +4. Change directory: `cd api/` +5. Run tests: `python -m pytest -v --cov-report term-missing --cov=app` + ## Environment variables ``` # Flask secret key From 1f2487f1c122bb57148b3e562ccbfb7b0d1bb5f1 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Wed, 30 Mar 2022 11:10:48 +0200 Subject: [PATCH 107/263] Refactoring --- api/tests/conftest.py | 3 -- api/tests/functional/helper_functions.py | 11 ++++++ api/tests/functional/test_keyword.py | 11 +----- api/tests/functional/test_portfolio.py | 11 +----- api/tests/functional/test_share.py | 11 +----- api/tests/functional/test_telegram.py | 11 +----- api/tests/functional/test_transaction.py | 11 +----- api/tests/functional/test_user.py | 11 +----- api/tests/unit/test_auth.py | 3 -- api/tests/unit/test_helper_functions.py | 47 ------------------------ 10 files changed, 17 insertions(+), 113 deletions(-) create mode 100644 api/tests/functional/helper_functions.py diff --git a/api/tests/conftest.py b/api/tests/conftest.py index c772474..e1f32fc 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -1,6 +1,3 @@ -import json -import os - import pytest from app import create_app, db from app.models import User, Transaction, Keyword, Share diff --git a/api/tests/functional/helper_functions.py b/api/tests/functional/helper_functions.py new file mode 100644 index 0000000..9e68226 --- /dev/null +++ b/api/tests/functional/helper_functions.py @@ -0,0 +1,11 @@ +import json + + +def get_token(test_client, email, password): + response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') + + if "data" in json.loads(response.data): + if "token" in json.loads(response.data)["data"]: + return json.loads(response.data)["data"]["token"] + + return "" \ No newline at end of file diff --git a/api/tests/functional/test_keyword.py b/api/tests/functional/test_keyword.py index 72763ca..3517c18 100644 --- a/api/tests/functional/test_keyword.py +++ b/api/tests/functional/test_keyword.py @@ -2,6 +2,7 @@ This file (test_keyword.py) contains the functional tests for the `keyword` blueprint. """ import json +from tests.functional.helper_functions import get_token def test_add_keyword_not_logged_in(test_client, init_database): @@ -179,13 +180,3 @@ def test_delete_keyword_user1_logged_in_keyword_not_exists(test_client, init_dat content_type='application/json') assert response.status_code == 500 assert b'Keyword doesn\'t exist for this user' in response.data - - -def get_token(test_client, email, password): - response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') - - if "data" in json.loads(response.data): - if "token" in json.loads(response.data)["data"]: - return json.loads(response.data)["data"]["token"] - - return "" diff --git a/api/tests/functional/test_portfolio.py b/api/tests/functional/test_portfolio.py index 10fd8cc..f240c93 100644 --- a/api/tests/functional/test_portfolio.py +++ b/api/tests/functional/test_portfolio.py @@ -2,6 +2,7 @@ This file (test_portfolio.py) contains the functional tests for the `portfolio` blueprint. """ import json +from tests.functional.helper_functions import get_token def test_get_portfolio_not_logged_in_empty_response(test_client, init_database): @@ -47,13 +48,3 @@ def test_get_portfolio_user1_logged_in_response_data(test_client, init_database) assert response.status_code == 200 assert b'"data":[]' not in response.data assert b'"data":[' in response.data - - -def get_token(test_client, email, password): - response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') - - if "data" in json.loads(response.data): - if "token" in json.loads(response.data)["data"]: - return json.loads(response.data)["data"]["token"] - - return "" diff --git a/api/tests/functional/test_share.py b/api/tests/functional/test_share.py index 61772fa..8df2ced 100644 --- a/api/tests/functional/test_share.py +++ b/api/tests/functional/test_share.py @@ -2,6 +2,7 @@ This file (test_share.py) contains the functional tests for the `share` blueprint. """ import json +from tests.functional.helper_functions import get_token def test_add_share_not_logged_in(test_client, init_database): @@ -179,13 +180,3 @@ def test_delete_share_user1_logged_in_symbol_not_exists(test_client, init_databa content_type='application/json') assert response.status_code == 500 assert b'Symbol doesn\'t exist for this user' in response.data - - -def get_token(test_client, email, password): - response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') - - if "data" in json.loads(response.data): - if "token" in json.loads(response.data)["data"]: - return json.loads(response.data)["data"]["token"] - - return "" diff --git a/api/tests/functional/test_telegram.py b/api/tests/functional/test_telegram.py index 756f6cd..34acee7 100644 --- a/api/tests/functional/test_telegram.py +++ b/api/tests/functional/test_telegram.py @@ -2,6 +2,7 @@ This file (test_telegram.py) contains the functional tests for the `telegram` blueprint. """ import json +from tests.functional.helper_functions import get_token def test_add_telegram_not_logged_in(test_client, init_database): @@ -54,13 +55,3 @@ def test_add_telegram_user1_logged_in_user_data_empty(test_client, init_database content_type='application/json') assert response.status_code == 400 assert b'missing' in response.data - - -def get_token(test_client, email, password): - response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') - - if "data" in json.loads(response.data): - if "token" in json.loads(response.data)["data"]: - return json.loads(response.data)["data"]["token"] - - return "" diff --git a/api/tests/functional/test_transaction.py b/api/tests/functional/test_transaction.py index d50578a..cb7b9ad 100644 --- a/api/tests/functional/test_transaction.py +++ b/api/tests/functional/test_transaction.py @@ -2,6 +2,7 @@ This file (test_transaction.py) contains the functional tests for the `transaction` blueprint. """ import json +from tests.functional.helper_functions import get_token def test_add_transaction_not_logged_in(test_client, init_database): @@ -91,13 +92,3 @@ def test_get_transaction_user1_logged_in_response_data(test_client, init_databas content_type='application/json') assert response.status_code == 200 assert b'Successfully loaded transactions' in response.data - - -def get_token(test_client, email, password): - response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') - - if "data" in json.loads(response.data): - if "token" in json.loads(response.data)["data"]: - return json.loads(response.data)["data"]["token"] - - return "" diff --git a/api/tests/functional/test_user.py b/api/tests/functional/test_user.py index 6a88089..8b57338 100644 --- a/api/tests/functional/test_user.py +++ b/api/tests/functional/test_user.py @@ -2,6 +2,7 @@ This file (test_user.py) contains the functional tests for the `users` blueprint. """ import json +from tests.functional.helper_functions import get_token def test_login_with_valid_data(test_client, init_database): @@ -501,13 +502,3 @@ def test_get_users_admin1_logged_in(test_client, init_database): content_type='application/json') assert response.status_code == 200 assert b'Successfully received all users' in response.data - - -def get_token(test_client, email, password): - response = test_client.post('/api/user/login', data=json.dumps(dict(email=email, password=password)), content_type='application/json') - - if "data" in json.loads(response.data): - if "token" in json.loads(response.data)["data"]: - return json.loads(response.data)["data"]["token"] - - return "" diff --git a/api/tests/unit/test_auth.py b/api/tests/unit/test_auth.py index cc996cd..cf35577 100644 --- a/api/tests/unit/test_auth.py +++ b/api/tests/unit/test_auth.py @@ -1,9 +1,6 @@ """ This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. """ -import datetime - -import jwt from app.auth import * diff --git a/api/tests/unit/test_helper_functions.py b/api/tests/unit/test_helper_functions.py index e5b4b2c..5cf054b 100644 --- a/api/tests/unit/test_helper_functions.py +++ b/api/tests/unit/test_helper_functions.py @@ -1,9 +1,6 @@ """ This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. """ -import datetime - -import jwt from app.helper_functions import * @@ -21,47 +18,3 @@ def test_check_password(): hashed = hash_password("password") assert check_password(hashed, "password".encode("utf-8")) is True assert check_password(hashed, "password1".encode("utf-8")) is False - - -def test_get_email_from_token_data(test_client, init_database): - """ - Test get_email_from_token_data function - """ - - # Invalid token - # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer XXXXX'}) - # request_ctx.push() - # assert get_email_from_token_data("SECRET_KEY") is None - # - # # empty token - # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer'}) - # request_ctx.push() - # assert get_email_from_token_data("SECRET_KEY") is None - # - # # valid token - # exp = datetime.datetime.utcnow() + datetime.timedelta(days=365) - # token = jwt.encode({'email': "user@example.com", 'exp': exp}, "SECRET_KEY", "HS256") - # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token}) - # request_ctx.push() - # assert get_email_from_token_data("SECRET_KEY") == "user@example.com" - # - # # expired token - # exp = datetime.datetime.utcnow() + datetime.timedelta(days=-1) - # token = jwt.encode({'email': "user@example.com", 'exp': exp}, "SECRET_KEY", "HS256") - # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token}) - # request_ctx.push() - # assert get_email_from_token_data("SECRET_KEY") is None - # - # # bot token (includes ":") but user doesn't exist - # exp = datetime.datetime.utcnow() + datetime.timedelta(days=365) - # token = jwt.encode({'email': "bot@example.com", 'exp': exp}, "SECRET_KEY", "HS256") - # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token + ':12345'}) - # request_ctx.push() - # assert get_email_from_token_data("SECRET_KEY") is None - - # # bot token (includes ":") user exists - # exp = datetime.datetime.utcnow() + datetime.timedelta(days=365) - # token = jwt.encode({'email': "bot@example.com", 'exp': exp}, "SECRET_KEY", "HS256") - # request_ctx = test_client.test_request_context(headers={'Authorization': 'Bearer ' + token + ':12345'}) - # request_ctx.push() - # assert get_email_from_token_data("SECRET_KEY") is None \ No newline at end of file From 041cd16450cebef0460c4fa842bbaa2b8ed1c940 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Wed, 30 Mar 2022 11:11:02 +0200 Subject: [PATCH 108/263] Fixed test config --- api/app/config/flask_test.cfg | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/api/app/config/flask_test.cfg b/api/app/config/flask_test.cfg index 01ebea6..2cc085b 100644 --- a/api/app/config/flask_test.cfg +++ b/api/app/config/flask_test.cfg @@ -17,10 +17,10 @@ SQLALCHEMY_ENGINE_OPTIONS = { BASE_RESPONSE_DATA_KEY = "data" BASE_RESPONSE_SCHEMA = BaseResponseSchema -BOT_EMAIL = os.getenv('BOT_EMAIL') -BOT_USERNAME = os.getenv('BOT_USERNAME') -BOT_PASSWORD = os.getenv('BOT_PASSWORD') +BOT_EMAIL = "bot1@example.com" +BOT_USERNAME = "bot1" +BOT_PASSWORD = "bot1" -ADMIN_EMAIL = os.getenv('ADMIN_EMAIL') -ADMIN_USERNAME = os.getenv('ADMIN_USERNAME') -ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD') +ADMIN_EMAIL = "admin1@example.com" +ADMIN_USERNAME = "admin1" +ADMIN_PASSWORD = "admin1" From 985f5158aedb5e8efe06d39b61bead93a7e168c4 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Wed, 30 Mar 2022 11:11:14 +0200 Subject: [PATCH 109/263] Updated requirements.txt --- api/requirements.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/requirements.txt b/api/requirements.txt index 16da00c..e25210c 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -8,5 +8,6 @@ pyjwt==2.3.0 apiflask==0.12.0 flask-cors==3.0.10 bcrypt==3.2.0 -pytest -pytest-cov \ No newline at end of file +pytest~=7.1.1 +pytest-cov +marshmallow~=3.15.0 \ No newline at end of file From 7dc903d583d832646fbe2189892f350895a5c700 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Wed, 30 Mar 2022 11:18:03 +0200 Subject: [PATCH 110/263] Add tests to pipeline --- .woodpecker/pipeline.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 56f8cf8..34a73c9 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -14,6 +14,16 @@ pipeline: event: pull_request # -------------------------------------- API -------------------------------------- + test_api: + image: python + commands: + - cd api/ + - pip install -r requirements.txt + - python -m pytest + when: + path: "api/*" + event: push + build_api: image: woodpeckerci/plugin-docker-buildx settings: From 7f0addfecb4be3e53391b47cc1188731f63d5d7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 10:31:50 +0000 Subject: [PATCH 111/263] Bump @angular-devkit/build-angular from 13.3.0 to 13.3.1 in /frontend Bumps [@angular-devkit/build-angular](https://github.com/angular/angular-cli) from 13.3.0 to 13.3.1. - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/13.3.0...13.3.1) --- updated-dependencies: - dependency-name: "@angular-devkit/build-angular" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 264 +++++++++++++++++++++++++++---------- frontend/package.json | 2 +- 2 files changed, 199 insertions(+), 67 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0a65e59..2f4858e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,7 +24,7 @@ "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.3.0", + "@angular-devkit/build-angular": "~13.3.1", "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", @@ -85,15 +85,15 @@ "dev": true }, "node_modules/@angular-devkit/build-angular": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.0.tgz", - "integrity": "sha512-3Ji7EeqGHj7i1Jgmeo3aIEXsnfKyFeQPpl65gcYmHwj5dP4lZzLSU4rMaWWUKksccgqCUXgPI2vKePTPazmikg==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.1.tgz", + "integrity": "sha512-xxBW4zZZM+lewW0nEpk9SXw6BMYhxe8WI/FjyEroOV8G2IuOrjZ4112QOpk6jCgmPHSOEldbltEdwoVLAnu09Q==", "dev": true, "dependencies": { "@ampproject/remapping": "1.1.1", - "@angular-devkit/architect": "0.1303.0", - "@angular-devkit/build-webpack": "0.1303.0", - "@angular-devkit/core": "13.3.0", + "@angular-devkit/architect": "0.1303.1", + "@angular-devkit/build-webpack": "0.1303.1", + "@angular-devkit/core": "13.3.1", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -104,7 +104,7 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.3.0", + "@ngtools/webpack": "13.3.1", "ansi-colors": "4.1.1", "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", @@ -138,7 +138,7 @@ "regenerator-runtime": "0.13.9", "resolve-url-loader": "5.0.0", "rxjs": "6.6.7", - "sass": "1.49.0", + "sass": "1.49.9", "sass-loader": "12.4.0", "semver": "7.3.5", "source-map-loader": "3.0.1", @@ -194,6 +194,48 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", + "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -213,12 +255,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1303.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.0.tgz", - "integrity": "sha512-a+Veg2oYn3RM2Kl148BReuONmD1kjbbYBnMUVi8nD6rvJPStFZkqN5s5ZkYybKeWnzMGaB3VasKR88z5XeH22A==", + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.1.tgz", + "integrity": "sha512-KSnR3y2q5hxh7t7ZSi0Emv/Kh9+D105JaEeyEqjqRjLdZSd2m6eAxbSUMNOAsbqnJTMCfzU5AG7jhbujuge0dQ==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1303.0", + "@angular-devkit/architect": "0.1303.1", "rxjs": "6.6.7" }, "engines": { @@ -231,6 +273,48 @@ "webpack-dev-server": "^4.0.0" } }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", + "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -2406,9 +2490,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.0.tgz", - "integrity": "sha512-QbTQWXK2WzYU+aKKVDG0ya7WYT+6rNAUXVt5ov9Nz1SGgDeozpiOx8ZqPWUvnToTY8EoodwWFGCVtkLHXUR+wA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.1.tgz", + "integrity": "sha512-40iEqAA/l882MPbGuG5EYxzsPWJ37fT4fF22SkPLX2eBgNhJ4K8XMt0gqcFhkHUsSe63frg1qjQ1Pd31msu0bQ==", "dev": true, "engines": { "node": "^12.20.0 || ^14.15.0 || >=16.10.0", @@ -6055,9 +6139,9 @@ } }, "node_modules/html-entities": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz", - "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", "dev": true }, "node_modules/html-escaper": { @@ -7868,9 +7952,9 @@ "optional": true }, "node_modules/node-forge": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", - "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "dev": true, "engines": { "node": ">= 6.13.0" @@ -8739,12 +8823,12 @@ } }, "node_modules/portfinder/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "dependencies": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" @@ -9776,9 +9860,9 @@ "dev": true }, "node_modules/sass": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.0.tgz", - "integrity": "sha512-TVwVdNDj6p6b4QymJtNtRS2YtLJ/CqZriGg0eIAbAKMlN8Xy6kbv33FsEZSF7FufFFM705SQviHjjThfaQ4VNw==", + "version": "1.49.9", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz", + "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -9789,7 +9873,7 @@ "sass": "sass.js" }, "engines": { - "node": ">=8.9.0" + "node": ">=12.0.0" } }, "node_modules/sass-loader": { @@ -9888,12 +9972,12 @@ "dev": true }, "node_modules/selfsigned": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.0.tgz", - "integrity": "sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", "dev": true, "dependencies": { - "node-forge": "^1.2.0" + "node-forge": "^1" }, "engines": { "node": ">=10" @@ -11459,15 +11543,15 @@ } }, "@angular-devkit/build-angular": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.0.tgz", - "integrity": "sha512-3Ji7EeqGHj7i1Jgmeo3aIEXsnfKyFeQPpl65gcYmHwj5dP4lZzLSU4rMaWWUKksccgqCUXgPI2vKePTPazmikg==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.1.tgz", + "integrity": "sha512-xxBW4zZZM+lewW0nEpk9SXw6BMYhxe8WI/FjyEroOV8G2IuOrjZ4112QOpk6jCgmPHSOEldbltEdwoVLAnu09Q==", "dev": true, "requires": { "@ampproject/remapping": "1.1.1", - "@angular-devkit/architect": "0.1303.0", - "@angular-devkit/build-webpack": "0.1303.0", - "@angular-devkit/core": "13.3.0", + "@angular-devkit/architect": "0.1303.1", + "@angular-devkit/build-webpack": "0.1303.1", + "@angular-devkit/core": "13.3.1", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -11478,7 +11562,7 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.3.0", + "@ngtools/webpack": "13.3.1", "ansi-colors": "4.1.1", "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", @@ -11513,7 +11597,7 @@ "regenerator-runtime": "0.13.9", "resolve-url-loader": "5.0.0", "rxjs": "6.6.7", - "sass": "1.49.0", + "sass": "1.49.9", "sass-loader": "12.4.0", "semver": "7.3.5", "source-map-loader": "3.0.1", @@ -11531,6 +11615,30 @@ "webpack-subresource-integrity": "5.1.0" }, "dependencies": { + "@angular-devkit/architect": { + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", + "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.1", + "rxjs": "6.6.7" + } + }, + "@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -11551,15 +11659,39 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.1303.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.0.tgz", - "integrity": "sha512-a+Veg2oYn3RM2Kl148BReuONmD1kjbbYBnMUVi8nD6rvJPStFZkqN5s5ZkYybKeWnzMGaB3VasKR88z5XeH22A==", + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.1.tgz", + "integrity": "sha512-KSnR3y2q5hxh7t7ZSi0Emv/Kh9+D105JaEeyEqjqRjLdZSd2m6eAxbSUMNOAsbqnJTMCfzU5AG7jhbujuge0dQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1303.0", + "@angular-devkit/architect": "0.1303.1", "rxjs": "6.6.7" }, "dependencies": { + "@angular-devkit/architect": { + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", + "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.1", + "rxjs": "6.6.7" + } + }, + "@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -13083,9 +13215,9 @@ } }, "@ngtools/webpack": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.0.tgz", - "integrity": "sha512-QbTQWXK2WzYU+aKKVDG0ya7WYT+6rNAUXVt5ov9Nz1SGgDeozpiOx8ZqPWUvnToTY8EoodwWFGCVtkLHXUR+wA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.1.tgz", + "integrity": "sha512-40iEqAA/l882MPbGuG5EYxzsPWJ37fT4fF22SkPLX2eBgNhJ4K8XMt0gqcFhkHUsSe63frg1qjQ1Pd31msu0bQ==", "dev": true, "requires": {} }, @@ -15842,9 +15974,9 @@ } }, "html-entities": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.2.tgz", - "integrity": "sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", "dev": true }, "html-escaper": { @@ -17190,9 +17322,9 @@ "optional": true }, "node-forge": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", - "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "dev": true }, "node-gyp": { @@ -17855,12 +17987,12 @@ } }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "requires": { - "minimist": "^1.2.5" + "minimist": "^1.2.6" } } } @@ -18581,9 +18713,9 @@ "dev": true }, "sass": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.0.tgz", - "integrity": "sha512-TVwVdNDj6p6b4QymJtNtRS2YtLJ/CqZriGg0eIAbAKMlN8Xy6kbv33FsEZSF7FufFFM705SQviHjjThfaQ4VNw==", + "version": "1.49.9", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz", + "integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", @@ -18652,12 +18784,12 @@ "dev": true }, "selfsigned": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.0.tgz", - "integrity": "sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.0.1.tgz", + "integrity": "sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ==", "dev": true, "requires": { - "node-forge": "^1.2.0" + "node-forge": "^1" } }, "semver": { diff --git a/frontend/package.json b/frontend/package.json index 5e381a2..d29ca7e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,7 +26,7 @@ "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.3.0", + "@angular-devkit/build-angular": "~13.3.1", "@angular/cli": "~13.3.0", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", From 2900d953e5a83a1a382949ce039594e457c4fd93 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 10:32:06 +0000 Subject: [PATCH 112/263] Update flask requirement from ~=2.1.0 to ~=2.1.1 in /api Updates the requirements on [flask](https://github.com/pallets/flask) to permit the latest version. - [Release notes](https://github.com/pallets/flask/releases) - [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/flask/compare/2.1.0...2.1.1) --- updated-dependencies: - dependency-name: flask dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- api/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/requirements.txt b/api/requirements.txt index a73b603..2803098 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,4 +1,4 @@ -Flask~=2.1.0 +Flask~=2.1.1 python-dotenv==0.20.0 uwsgi==2.0.20 Flask_SQLAlchemy==2.5.1 From 708518ee5c6501986e8d50c4d79da1feab0ea733 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 10:32:07 +0000 Subject: [PATCH 113/263] Bump @angular/cli from 13.3.0 to 13.3.1 in /frontend Bumps [@angular/cli](https://github.com/angular/angular-cli) from 13.3.0 to 13.3.1. - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/13.3.0...13.3.1) --- updated-dependencies: - dependency-name: "@angular/cli" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 284 ++++++++++++++++++++++++++++++++----- frontend/package.json | 2 +- 2 files changed, 252 insertions(+), 34 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0a65e59..b8b1c74 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,7 +25,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "~13.3.0", - "@angular/cli": "~13.3.0", + "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", "@types/node": "^17.0.23", @@ -295,12 +295,12 @@ "dev": true }, "node_modules/@angular-devkit/schematics": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.0.tgz", - "integrity": "sha512-hq7tqnB3uVT/iDgqWWZ4kvnijeAcgd4cfLzZiCPaYn1nuhZf0tWsho6exhJ/odMZHvVp7w8OibqWiUKxNY9zHA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.1.tgz", + "integrity": "sha512-DxXMjlq/sALcHuONZRMTBX5k30XPfN4b6Ue4k7Xl8JKZqyHhEzfXaZzgD9u2cwb7wybKEeF/BZ5eJd8JG525og==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.3.0", + "@angular-devkit/core": "13.3.1", "jsonc-parser": "3.0.0", "magic-string": "0.25.7", "ora": "5.4.1", @@ -312,6 +312,33 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/schematics/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -367,16 +394,16 @@ "optional": true }, "node_modules/@angular/cli": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.0.tgz", - "integrity": "sha512-2qCKP/QsyxrJnpd3g4P/iTQ4TjI04N8r+bG5YLLfudoMDsQ/Ti4ogdI7PBeG2IMbRylZW9XLjHraWG42+Y9tWw==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.1.tgz", + "integrity": "sha512-0uwU8v3V/2s95X4cZT582J6upReT/ZNw/VAf4p4q51JN+BBvdCEb251xTF+TcOojyToFyJYvg8T28XSrsNsmTQ==", "dev": true, "hasInstallScript": true, "dependencies": { - "@angular-devkit/architect": "0.1303.0", - "@angular-devkit/core": "13.3.0", - "@angular-devkit/schematics": "13.3.0", - "@schematics/angular": "13.3.0", + "@angular-devkit/architect": "0.1303.1", + "@angular-devkit/core": "13.3.1", + "@angular-devkit/schematics": "13.3.1", + "@schematics/angular": "13.3.1", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -402,6 +429,66 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", + "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular/cli/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular/cli/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/@angular/common": { "version": "13.2.6", "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.2.6.tgz", @@ -2564,13 +2651,13 @@ } }, "node_modules/@schematics/angular": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.0.tgz", - "integrity": "sha512-WND6DXWf0ZFefqlC2hUm1FzHDonRfGpDEPWVhVulhYkB7IUUaXuCz8K41HAScyJ3bxUngs2Lx9+4omikc05fxA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.1.tgz", + "integrity": "sha512-+lrK/d1eJsAK6d6E9TDeg3Vc71bDy1KsE8M+lEINdX9Wax22mAz4pw20X9RSCw5RHgn+XcNUuMsgRJAwVhDNpg==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.3.0", - "@angular-devkit/schematics": "13.3.0", + "@angular-devkit/core": "13.3.1", + "@angular-devkit/schematics": "13.3.1", "jsonc-parser": "3.0.0" }, "engines": { @@ -2579,6 +2666,51 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@schematics/angular/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@schematics/angular/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/@socket.io/base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", @@ -11609,18 +11741,32 @@ } }, "@angular-devkit/schematics": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.0.tgz", - "integrity": "sha512-hq7tqnB3uVT/iDgqWWZ4kvnijeAcgd4cfLzZiCPaYn1nuhZf0tWsho6exhJ/odMZHvVp7w8OibqWiUKxNY9zHA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.1.tgz", + "integrity": "sha512-DxXMjlq/sALcHuONZRMTBX5k30XPfN4b6Ue4k7Xl8JKZqyHhEzfXaZzgD9u2cwb7wybKEeF/BZ5eJd8JG525og==", "dev": true, "requires": { - "@angular-devkit/core": "13.3.0", + "@angular-devkit/core": "13.3.1", "jsonc-parser": "3.0.0", "magic-string": "0.25.7", "ora": "5.4.1", "rxjs": "6.6.7" }, "dependencies": { + "@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -11664,15 +11810,15 @@ } }, "@angular/cli": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.0.tgz", - "integrity": "sha512-2qCKP/QsyxrJnpd3g4P/iTQ4TjI04N8r+bG5YLLfudoMDsQ/Ti4ogdI7PBeG2IMbRylZW9XLjHraWG42+Y9tWw==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.1.tgz", + "integrity": "sha512-0uwU8v3V/2s95X4cZT582J6upReT/ZNw/VAf4p4q51JN+BBvdCEb251xTF+TcOojyToFyJYvg8T28XSrsNsmTQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1303.0", - "@angular-devkit/core": "13.3.0", - "@angular-devkit/schematics": "13.3.0", - "@schematics/angular": "13.3.0", + "@angular-devkit/architect": "0.1303.1", + "@angular-devkit/core": "13.3.1", + "@angular-devkit/schematics": "13.3.1", + "@schematics/angular": "13.3.1", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -11688,6 +11834,47 @@ "semver": "7.3.5", "symbol-observable": "4.0.0", "uuid": "8.3.2" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.1303.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", + "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.1", + "rxjs": "6.6.7" + } + }, + "@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "@angular/common": { @@ -13206,14 +13393,45 @@ "peer": true }, "@schematics/angular": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.0.tgz", - "integrity": "sha512-WND6DXWf0ZFefqlC2hUm1FzHDonRfGpDEPWVhVulhYkB7IUUaXuCz8K41HAScyJ3bxUngs2Lx9+4omikc05fxA==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.1.tgz", + "integrity": "sha512-+lrK/d1eJsAK6d6E9TDeg3Vc71bDy1KsE8M+lEINdX9Wax22mAz4pw20X9RSCw5RHgn+XcNUuMsgRJAwVhDNpg==", "dev": true, "requires": { - "@angular-devkit/core": "13.3.0", - "@angular-devkit/schematics": "13.3.0", + "@angular-devkit/core": "13.3.1", + "@angular-devkit/schematics": "13.3.1", "jsonc-parser": "3.0.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", + "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "@socket.io/base64-arraybuffer": { diff --git a/frontend/package.json b/frontend/package.json index 5e381a2..568c54a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,7 +27,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "~13.3.0", - "@angular/cli": "~13.3.0", + "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.0", "@types/node": "^17.0.23", From 3b8e970ad98666fd4a084578a150eb729416d61c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 10:32:53 +0000 Subject: [PATCH 114/263] Bump @angular/material from 13.3.1 to 13.3.2 in /frontend Bumps [@angular/material](https://github.com/angular/components) from 13.3.1 to 13.3.2. - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/13.3.1...13.3.2) --- updated-dependencies: - dependency-name: "@angular/material" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 28 ++++++++++++++-------------- frontend/package.json | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0a65e59..1555d29 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,7 +14,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.3.1", + "@angular/material": "^13.3.2", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", @@ -345,9 +345,9 @@ } }, "node_modules/@angular/cdk": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.1.tgz", - "integrity": "sha512-JUCvhN6XomY5JOMH7Qyj/JodAuAtKgLlJYKs0tPmjCkGcBdR/T3/hOJ9gSRhz8PCOoiEORj+3xCh547oJRyesA==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.2.tgz", + "integrity": "sha512-Rb+SvQpjXqa0kedk/6nG57f8dC4uG1q35SR6mt6jCz84ikyS2zhikVbzaxLdG15uDvq1+N5Vx3NTSgH19XUSug==", "dependencies": { "tslib": "^2.3.0" }, @@ -577,15 +577,15 @@ } }, "node_modules/@angular/material": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.1.tgz", - "integrity": "sha512-c9BYNxvZvxuCA5QticqfEhn3nuspUnwxIVejjO5ZTqKlkK+vQGKN3QhshHLrMHeFYyjao/E8jVnJX7ahitPM9w==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.2.tgz", + "integrity": "sha512-agIuNcN1wO05ODEXc0wwrMK2ydnhPGO8tcBBCJXrDgiA/ktwm+WtfOsiZwPoev7aJ8pactVhxT5R0bJ6vW2SmA==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "^13.0.0 || ^14.0.0-0", - "@angular/cdk": "13.3.1", + "@angular/cdk": "13.3.2", "@angular/common": "^13.0.0 || ^14.0.0-0", "@angular/core": "^13.0.0 || ^14.0.0-0", "@angular/forms": "^13.0.0 || ^14.0.0-0", @@ -11647,9 +11647,9 @@ } }, "@angular/cdk": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.1.tgz", - "integrity": "sha512-JUCvhN6XomY5JOMH7Qyj/JodAuAtKgLlJYKs0tPmjCkGcBdR/T3/hOJ9gSRhz8PCOoiEORj+3xCh547oJRyesA==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.2.tgz", + "integrity": "sha512-Rb+SvQpjXqa0kedk/6nG57f8dC4uG1q35SR6mt6jCz84ikyS2zhikVbzaxLdG15uDvq1+N5Vx3NTSgH19XUSug==", "requires": { "parse5": "^5.0.0", "tslib": "^2.3.0" @@ -11809,9 +11809,9 @@ } }, "@angular/material": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.1.tgz", - "integrity": "sha512-c9BYNxvZvxuCA5QticqfEhn3nuspUnwxIVejjO5ZTqKlkK+vQGKN3QhshHLrMHeFYyjao/E8jVnJX7ahitPM9w==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.2.tgz", + "integrity": "sha512-agIuNcN1wO05ODEXc0wwrMK2ydnhPGO8tcBBCJXrDgiA/ktwm+WtfOsiZwPoev7aJ8pactVhxT5R0bJ6vW2SmA==", "requires": { "tslib": "^2.3.0" } diff --git a/frontend/package.json b/frontend/package.json index 5e381a2..d97c6f2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.3.1", + "@angular/material": "^13.3.2", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", From 0190b8d6332a26ba4ba112221a88aeaafda3f33d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 16:11:01 +0000 Subject: [PATCH 115/263] Bump @types/jasmine from 4.0.0 to 4.0.1 in /frontend Bumps [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine) --- updated-dependencies: - dependency-name: "@types/jasmine" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 14 +++++++------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7d15060..f0ea7a0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -27,7 +27,7 @@ "@angular-devkit/build-angular": "~13.3.0", "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", - "@types/jasmine": "~4.0.0", + "@types/jasmine": "~4.0.1", "@types/node": "^17.0.23", "jasmine-core": "~4.0.0", "karma": "~6.3.0", @@ -2844,9 +2844,9 @@ } }, "node_modules/@types/jasmine": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.0.tgz", - "integrity": "sha512-KvhqNz4NaONk7cfp4E9x+uXOUp7x4H2Zeyb4yXnw2vIuxD5YfSi1767x+aF7z54elhZcC0OH9/78/WL6+5jcDg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.1.tgz", + "integrity": "sha512-6KtN9P42PfhUSxybWY0iG6wpMJEoMMXyd9qi06EiOf5p6fOwAj9t/BwBEkx0Ys47oxAbMKv9sKqTV9igHYeUsQ==", "dev": true }, "node_modules/@types/json-schema": { @@ -13561,9 +13561,9 @@ } }, "@types/jasmine": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.0.tgz", - "integrity": "sha512-KvhqNz4NaONk7cfp4E9x+uXOUp7x4H2Zeyb4yXnw2vIuxD5YfSi1767x+aF7z54elhZcC0OH9/78/WL6+5jcDg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.1.tgz", + "integrity": "sha512-6KtN9P42PfhUSxybWY0iG6wpMJEoMMXyd9qi06EiOf5p6fOwAj9t/BwBEkx0Ys47oxAbMKv9sKqTV9igHYeUsQ==", "dev": true }, "@types/json-schema": { diff --git a/frontend/package.json b/frontend/package.json index f0648ce..a393af2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,7 +29,7 @@ "@angular-devkit/build-angular": "~13.3.0", "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", - "@types/jasmine": "~4.0.0", + "@types/jasmine": "~4.0.1", "@types/node": "^17.0.23", "jasmine-core": "~4.0.0", "karma": "~6.3.0", From 61d88c101bbc3b1872939a340204e2276879fa3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Apr 2022 09:30:00 +0000 Subject: [PATCH 116/263] Bump @types/jasmine from 4.0.1 to 4.0.2 in /frontend Bumps [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) from 4.0.1 to 4.0.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine) --- updated-dependencies: - dependency-name: "@types/jasmine" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 150 ++----------------------------------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 144 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index db6af1c..2cc45ca 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -27,7 +27,7 @@ "@angular-devkit/build-angular": "~13.3.1", "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", - "@types/jasmine": "~4.0.1", + "@types/jasmine": "~4.0.2", "@types/node": "^17.0.23", "jasmine-core": "~4.0.0", "karma": "~6.3.0", @@ -51,39 +51,6 @@ "node": ">=6.0.0" } }, - "node_modules/@angular-devkit/architect": { - "version": "0.1303.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.0.tgz", - "integrity": "sha512-kTcKB917ICA8j53SGo4gn+qAlzx8si+iHnOTbp5QlMr7qt/Iz07SVVI8mRlMD6c6lr7eE/fVlCLzEZ1+WCQpTA==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "13.3.0", - "rxjs": "6.6.7" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/architect/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/architect/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular-devkit/build-angular": { "version": "13.3.1", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.1.tgz", @@ -333,51 +300,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/@angular-devkit/core": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", - "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", - "dev": true, - "dependencies": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/core/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular-devkit/schematics": { "version": "13.3.1", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.1.tgz", @@ -2928,9 +2850,9 @@ } }, "node_modules/@types/jasmine": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.1.tgz", - "integrity": "sha512-6KtN9P42PfhUSxybWY0iG6wpMJEoMMXyd9qi06EiOf5p6fOwAj9t/BwBEkx0Ys47oxAbMKv9sKqTV9igHYeUsQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.2.tgz", + "integrity": "sha512-mSPIWhDyQ4nzYdR6Ixy15VhVKMVw93mSUlQxxpVb4S9Hj90lBvg+7kkBw23uYcv8CESPPXit+u3cARYcPeC8Jg==", "dev": true }, "node_modules/@types/json-schema": { @@ -11647,33 +11569,6 @@ "sourcemap-codec": "1.4.8" } }, - "@angular-devkit/architect": { - "version": "0.1303.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.0.tgz", - "integrity": "sha512-kTcKB917ICA8j53SGo4gn+qAlzx8si+iHnOTbp5QlMr7qt/Iz07SVVI8mRlMD6c6lr7eE/fVlCLzEZ1+WCQpTA==", - "dev": true, - "requires": { - "@angular-devkit/core": "13.3.0", - "rxjs": "6.6.7" - }, - "dependencies": { - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "@angular-devkit/build-angular": { "version": "13.3.1", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.1.tgz", @@ -11841,37 +11736,6 @@ } } }, - "@angular-devkit/core": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", - "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "dependencies": { - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "@angular-devkit/schematics": { "version": "13.3.1", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.1.tgz", @@ -13693,9 +13557,9 @@ } }, "@types/jasmine": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.1.tgz", - "integrity": "sha512-6KtN9P42PfhUSxybWY0iG6wpMJEoMMXyd9qi06EiOf5p6fOwAj9t/BwBEkx0Ys47oxAbMKv9sKqTV9igHYeUsQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.2.tgz", + "integrity": "sha512-mSPIWhDyQ4nzYdR6Ixy15VhVKMVw93mSUlQxxpVb4S9Hj90lBvg+7kkBw23uYcv8CESPPXit+u3cARYcPeC8Jg==", "dev": true }, "@types/json-schema": { diff --git a/frontend/package.json b/frontend/package.json index 22187fd..d31fa67 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,7 +29,7 @@ "@angular-devkit/build-angular": "~13.3.1", "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", - "@types/jasmine": "~4.0.1", + "@types/jasmine": "~4.0.2", "@types/node": "^17.0.23", "jasmine-core": "~4.0.0", "karma": "~6.3.0", From 4379fac8d2b5c8dfe899daadd9f42662c9c6c18c Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Mon, 4 Apr 2022 13:30:04 +0200 Subject: [PATCH 117/263] Update .env.example --- api/.env.example | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/api/.env.example b/api/.env.example index af67b58..25dc0bf 100644 --- a/api/.env.example +++ b/api/.env.example @@ -7,3 +7,12 @@ MYSQL_PASSWORD= # Flask secret key SECRET_KEY= + +# Users +BOT_EMAIL= +BOT_USERNAME= +BOT_PASSWORD= + +ADMIN_EMAIL= +ADMIN_USERNAME= +ADMIN_PASSWORD= From 2aea0fe9799e0e33c5f3fbd37bea2d32d5dcb313 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 14:10:25 +0200 Subject: [PATCH 118/263] Try to fix database problems --- api/app/config/flask.cfg | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/app/config/flask.cfg b/api/app/config/flask.cfg index 1a6347c..25babd4 100644 --- a/api/app/config/flask.cfg +++ b/api/app/config/flask.cfg @@ -9,8 +9,9 @@ SECRET_KEY = os.getenv('SECRET_KEY', 'secret string') SQLALCHEMY_DATABASE_URI = "mysql+pymysql://" + os.getenv('MYSQL_USER') + ":" + os.getenv('MYSQL_PASSWORD') + "@" + os.getenv('MYSQL_HOST') + ":" + os.getenv('MYSQL_PORT', '3306') + "/" + os.getenv('MYSQL_NAME', 'aktienbot') SQLALCHEMY_TRACK_MODIFICATIONS = False # Avoids SQLAlchemy warning SQLALCHEMY_ENGINE_OPTIONS = { - 'pool_size': 100, - 'pool_recycle': 240 # 4 minutes + 'pool_size': 10, + 'pool_recycle': 60, + 'pool_pre_ping': True } # openapi/Swagger config From 1624989f77d7c7e80c6328b755438d327654bfab Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 14:16:56 +0200 Subject: [PATCH 119/263] Try to fix pipeline --- .woodpecker/pipeline.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 34a73c9..5825f6e 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -17,6 +17,10 @@ pipeline: test_api: image: python commands: + - export MYSQL_USER=aktienbot + - export MYSQL_PASSWORD=12345678 + - export MYSQL_HOST=mariadb + - export MYSQL_PORT=3306 - cd api/ - pip install -r requirements.txt - python -m pytest From 14b5929c8d3cbd1123fb435ca9d863b725b28692 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 14:17:51 +0200 Subject: [PATCH 120/263] Test pipeline --- api/app/db.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/app/db.py b/api/app/db.py index 890af1d..d09ee30 100644 --- a/api/app/db.py +++ b/api/app/db.py @@ -1,3 +1,4 @@ from flask_sqlalchemy import SQLAlchemy +# database object database = SQLAlchemy() From f7ece8e439ba65f3490ad3206f760381f20f7597 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 14:18:57 +0200 Subject: [PATCH 121/263] Test pipeline --- api/app.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/app.py b/api/app.py index 0424fca..0261ad1 100644 --- a/api/app.py +++ b/api/app.py @@ -1,6 +1,4 @@ from app import create_app -# Call the application factory function to construct a Flask application -# instance using the development configuration application = create_app('config/flask.cfg') application.run() From 884c3f98ccb73bd43b173903da3aac8066fd02b0 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 15:07:29 +0200 Subject: [PATCH 122/263] Detach api tests --- .woodpecker/pipeline.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 5825f6e..c8ebd9a 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -16,6 +16,7 @@ pipeline: # -------------------------------------- API -------------------------------------- test_api: image: python + detach: true commands: - export MYSQL_USER=aktienbot - export MYSQL_PASSWORD=12345678 From 2eb5fe133ec8267af7d048122efbcac497ad2653 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 15:11:07 +0200 Subject: [PATCH 123/263] Test pipeline --- api/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/app.py b/api/app.py index 0261ad1..3314930 100644 --- a/api/app.py +++ b/api/app.py @@ -1,4 +1,5 @@ from app import create_app +# Create an application instance that web servers can use. application = create_app('config/flask.cfg') application.run() From 6fd3f05ba2e826878b0c2170ff9962a40419f594 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 15:13:27 +0200 Subject: [PATCH 124/263] Disable tests --- .woodpecker/pipeline.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index c8ebd9a..4e5db94 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -14,20 +14,20 @@ pipeline: event: pull_request # -------------------------------------- API -------------------------------------- - test_api: - image: python - detach: true - commands: - - export MYSQL_USER=aktienbot - - export MYSQL_PASSWORD=12345678 - - export MYSQL_HOST=mariadb - - export MYSQL_PORT=3306 - - cd api/ - - pip install -r requirements.txt - - python -m pytest - when: - path: "api/*" - event: push +# test_api: +# image: python +# detach: true +# commands: +# - export MYSQL_USER=aktienbot +# - export MYSQL_PASSWORD=12345678 +# - export MYSQL_HOST=mariadb +# - export MYSQL_PORT=3306 +# - cd api/ +# - pip install -r requirements.txt +# - python -m pytest +# when: +# path: "api/*" +# event: push build_api: image: woodpeckerci/plugin-docker-buildx From 51c598d506db2b522f86a78108043f06f2c8f327 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 15:35:16 +0200 Subject: [PATCH 125/263] Update pipeline --- .woodpecker/pipeline.yml | 68 +++++++--------------------------------- 1 file changed, 11 insertions(+), 57 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 4e5db94..8cea7ba 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -6,29 +6,8 @@ pipeline: when: event: push - generate_docker_tag_pr: - image: golang - commands: - - echo -n "pr-${CI_COMMIT_SHA:0:8}, pr" > .tags - when: - event: pull_request # -------------------------------------- API -------------------------------------- -# test_api: -# image: python -# detach: true -# commands: -# - export MYSQL_USER=aktienbot -# - export MYSQL_PASSWORD=12345678 -# - export MYSQL_HOST=mariadb -# - export MYSQL_PORT=3306 -# - cd api/ -# - pip install -r requirements.txt -# - python -m pytest -# when: -# path: "api/*" -# event: push - build_api: image: woodpeckerci/plugin-docker-buildx settings: @@ -46,22 +25,6 @@ pipeline: path: "api/*" event: push - deploy_api: - image: appleboy/drone-ssh - network_mode: host - settings: - host: - from_secret: ssh_host - username: - from_secret: ssh_user - password: - from_secret: ssh_password - script: - - /opt/docker/TelegramAktienBot/deploy_api.sh - when: - path: "api/*" - event: push - # -------------------------------------- Bot -------------------------------------- build_bot: @@ -79,21 +42,7 @@ pipeline: platforms: linux/amd64 when: path: "telegram_bot/*" - - deploy_bot: - image: appleboy/drone-ssh - network_mode: host - settings: - host: - from_secret: ssh_host - username: - from_secret: ssh_user - password: - from_secret: ssh_password - script: - - /opt/docker/TelegramAktienBot/deploy_bot.sh - when: - path: "telegram_bot/*" + event: push # -------------------------------------- Frontend -------------------------------------- @@ -112,8 +61,11 @@ pipeline: platforms: linux/amd64 when: path: "frontend/*" - - deploy_frontend: + event: push + + + # -------------------------------------- Deploy -------------------------------------- + deploy: image: appleboy/drone-ssh network_mode: host settings: @@ -124,8 +76,10 @@ pipeline: password: from_secret: ssh_password script: - - /opt/docker/TelegramAktienBot/deploy_frontend.sh - when: - path: "frontend/*" + - cd /root/docker/aktienbot + - docker-compose up -p "aktienbot" --force-recreate --build -d + - docker image prune -f + when: + event: push branches: main From c9fcf736ffabce2641701efec6268bceaacd189d Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 15:38:50 +0200 Subject: [PATCH 126/263] Fixed pipeline --- .woodpecker/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 8cea7ba..d54a74f 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -77,7 +77,7 @@ pipeline: from_secret: ssh_password script: - cd /root/docker/aktienbot - - docker-compose up -p "aktienbot" --force-recreate --build -d + - docker-compose up --force-recreate --build -d - docker image prune -f when: event: push From d6b663e8c8abb0b76681ca974ae4297b6f57419e Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 15:51:43 +0200 Subject: [PATCH 127/263] Added comments to dockerfiles --- api/Dockerfile | 11 ++++++++++- frontend/Dockerfile | 10 ++++++++-- telegram_bot/Dockerfile | 10 +++++++--- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/api/Dockerfile b/api/Dockerfile index 2736a90..cfc57aa 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,19 +1,28 @@ FROM python:3.10-alpine +# Change the working directory to the project root WORKDIR /srv/flask_app + +# Install dependencies RUN apk add nginx build-base libffi-dev curl uwsgi +# Install python dependencies COPY api/requirements.txt /srv/flask_app/ - RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-location +# Copy the app COPY api /srv/flask_app COPY api/deploy/nginx.conf /etc/nginx + +# Change file permissions RUN chmod +x ./deploy/start.sh RUN chmod +x ./deploy/healthcheck.sh +# Set healthcheck HEALTHCHECK --interval=15s --timeout=2s CMD ["./deploy/healthcheck.sh"] +# Expose webserver port EXPOSE 80 +# Run the app CMD ["./deploy/start.sh"] diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 637f6aa..dc4d24f 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,24 +1,30 @@ FROM node:latest as build +# Change to the project directory WORKDIR /usr/local/app +# Copy the project files to the container COPY frontend /usr/local/app/ +# Install dependencies RUN npm install RUN npm run build -RUN ls /usr/local/app/dist - FROM nginx:latest +# Copy the project files to the container COPY --from=build /usr/local/app/dist/aktienbot /usr/share/nginx/html +# Copy configuration files COPY frontend/deploy/nginx.conf /etc/nginx COPY frontend/deploy deploy/ +# Change file permissions RUN chmod +x ./deploy/healthcheck.sh +# set healthcheck HEALTHCHECK --interval=15s --timeout=2s CMD ["./deploy/healthcheck.sh"] +# Expose webserver port EXPOSE 80 diff --git a/telegram_bot/Dockerfile b/telegram_bot/Dockerfile index dc51065..bb48e9a 100644 --- a/telegram_bot/Dockerfile +++ b/telegram_bot/Dockerfile @@ -1,17 +1,21 @@ FROM python:3.10-slim +# Change the working directory to the root of the project WORKDIR /srv/flask_app +# Install the dependencies COPY telegram_bot/requirements.txt /srv/flask_app/ - RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-location +# Copy the source code to the working directory COPY telegram_bot /srv/flask_app + +# Change file permissions RUN chmod +x ./deploy/start.sh RUN chmod +x ./deploy/healthcheck.sh +# TODO: Set healthcheck # HEALTHCHECK --interval=15s --timeout=2s CMD ["./deploy/healthcheck.sh"] -EXPOSE 80 - +# Run the application CMD ["./deploy/start.sh"] From 01f9a52ebd87ba1a8dea1b1540ae72874107cc9d Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:00:55 +0200 Subject: [PATCH 128/263] Update pipeline --- .woodpecker/pipeline.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index d54a74f..1d64667 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -76,9 +76,9 @@ pipeline: password: from_secret: ssh_password script: - - cd /root/docker/aktienbot - - docker-compose up --force-recreate --build -d - - docker image prune -f + - docker-clean + - docker-compose pull web + - docker-compose up -d web when: event: push From f234f8c91e573164b213d1e78ee73ea183dc87d4 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:02:44 +0200 Subject: [PATCH 129/263] Update pipeline --- .woodpecker/pipeline.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 1d64667..05f75d6 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -76,9 +76,9 @@ pipeline: password: from_secret: ssh_password script: - - docker-clean - - docker-compose pull web - - docker-compose up -d web + - cd /root/docker/aktienbot + - docker-compose pull + - docker-compose up -p "aktienbot" -d when: event: push From 1f0bff5ecc134ea8335359c49a8a5faa2f45f0d7 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:03:48 +0200 Subject: [PATCH 130/263] Update pipeline --- .woodpecker/pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 05f75d6..3ae905f 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -78,7 +78,7 @@ pipeline: script: - cd /root/docker/aktienbot - docker-compose pull - - docker-compose up -p "aktienbot" -d + - docker-compose -p "aktienbot" up -d when: event: push From 75ea2671d42c7e72417e222f5336e62edd7bbf63 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:05:55 +0200 Subject: [PATCH 131/263] Update healthcheck.sh --- api/deploy/healthcheck.sh | 1 + frontend/deploy/healthcheck.sh | 1 + telegram_bot/deploy/healthcheck.sh | 2 -- 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 telegram_bot/deploy/healthcheck.sh diff --git a/api/deploy/healthcheck.sh b/api/deploy/healthcheck.sh index 390f507..fffa122 100644 --- a/api/deploy/healthcheck.sh +++ b/api/deploy/healthcheck.sh @@ -1,2 +1,3 @@ #!/usr/bin/env sh + curl -s http://localhost:80/ -o /dev/null || exit 1 diff --git a/frontend/deploy/healthcheck.sh b/frontend/deploy/healthcheck.sh index 390f507..fffa122 100644 --- a/frontend/deploy/healthcheck.sh +++ b/frontend/deploy/healthcheck.sh @@ -1,2 +1,3 @@ #!/usr/bin/env sh + curl -s http://localhost:80/ -o /dev/null || exit 1 diff --git a/telegram_bot/deploy/healthcheck.sh b/telegram_bot/deploy/healthcheck.sh deleted file mode 100644 index 390f507..0000000 --- a/telegram_bot/deploy/healthcheck.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env sh -curl -s http://localhost:80/ -o /dev/null || exit 1 From 7b3624e41f2604067764d2db1e15eb3e75aec2f3 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:07:22 +0200 Subject: [PATCH 132/263] Update pipeline --- .woodpecker/pipeline.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 3ae905f..d202d96 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -22,7 +22,7 @@ pipeline: dockerfile: api/Dockerfile platforms: linux/amd64 when: - path: "api/*" + path: "api/**" event: push @@ -41,7 +41,7 @@ pipeline: dockerfile: telegram_bot/Dockerfile platforms: linux/amd64 when: - path: "telegram_bot/*" + path: "telegram_bot/**" event: push @@ -60,7 +60,7 @@ pipeline: dockerfile: frontend/Dockerfile platforms: linux/amd64 when: - path: "frontend/*" + path: "frontend/**" event: push From 11140aac971f6b60b7aa9a87afa208621bae60c6 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:08:08 +0200 Subject: [PATCH 133/263] Changed some file to test pipeline execution --- api/deploy/start.sh | 1 + frontend/deploy/nginx.conf | 1 - telegram_bot/deploy/start.sh | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api/deploy/start.sh b/api/deploy/start.sh index 77fb780..db51705 100644 --- a/api/deploy/start.sh +++ b/api/deploy/start.sh @@ -1,3 +1,4 @@ #!/usr/bin/env sh + nginx -g "daemon off;" & uwsgi --ini deploy/uwsgi.ini \ No newline at end of file diff --git a/frontend/deploy/nginx.conf b/frontend/deploy/nginx.conf index d8a200f..f1595bf 100644 --- a/frontend/deploy/nginx.conf +++ b/frontend/deploy/nginx.conf @@ -23,7 +23,6 @@ http { gzip_proxied any; gzip_types - ## text/html is always compressed : https://nginx.org/en/docs/http/ngx_http_gzip_module.html text/plain text/css text/javascript diff --git a/telegram_bot/deploy/start.sh b/telegram_bot/deploy/start.sh index 4ee7078..232326f 100644 --- a/telegram_bot/deploy/start.sh +++ b/telegram_bot/deploy/start.sh @@ -1,2 +1,3 @@ #!/usr/bin/env sh + python bot.py \ No newline at end of file From 00e27ba776c850edc000de37f1bb53ed21bf76eb Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:10:37 +0200 Subject: [PATCH 134/263] Fixed Dockerfile --- frontend/Dockerfile | 1 - telegram_bot/Dockerfile | 1 - 2 files changed, 2 deletions(-) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index dc4d24f..d487766 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -10,7 +10,6 @@ COPY frontend /usr/local/app/ RUN npm install RUN npm run build - FROM nginx:latest # Copy the project files to the container diff --git a/telegram_bot/Dockerfile b/telegram_bot/Dockerfile index bb48e9a..49dd01c 100644 --- a/telegram_bot/Dockerfile +++ b/telegram_bot/Dockerfile @@ -12,7 +12,6 @@ COPY telegram_bot /srv/flask_app # Change file permissions RUN chmod +x ./deploy/start.sh -RUN chmod +x ./deploy/healthcheck.sh # TODO: Set healthcheck # HEALTHCHECK --interval=15s --timeout=2s CMD ["./deploy/healthcheck.sh"] From ab28346c6f984a8a5381ac59efe811109e045dce Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:38:08 +0200 Subject: [PATCH 135/263] Added docker-compose and config files --- deploy/README.md | 3 ++ deploy/aktienbot/.env.api | 16 ++++++++ deploy/aktienbot/.env.bot | 3 ++ deploy/aktienbot/docker-compose.yml | 62 +++++++++++++++++++++++++++++ deploy/base/acme.json | 0 deploy/base/docker-compose.yml | 38 ++++++++++++++++++ deploy/base/traefik-dynamic.toml | 22 ++++++++++ deploy/base/traefik.toml | 44 ++++++++++++++++++++ 8 files changed, 188 insertions(+) create mode 100644 deploy/README.md create mode 100644 deploy/aktienbot/.env.api create mode 100644 deploy/aktienbot/.env.bot create mode 100644 deploy/aktienbot/docker-compose.yml create mode 100644 deploy/base/acme.json create mode 100644 deploy/base/docker-compose.yml create mode 100644 deploy/base/traefik-dynamic.toml create mode 100644 deploy/base/traefik.toml diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..e7cf9f2 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,3 @@ +# Deploy + +Files that are used for deployment. diff --git a/deploy/aktienbot/.env.api b/deploy/aktienbot/.env.api new file mode 100644 index 0000000..81a2bf6 --- /dev/null +++ b/deploy/aktienbot/.env.api @@ -0,0 +1,16 @@ +BOT_API_KEY= +SECRET_KEY= + +MYSQL_USER= +MYSQL_PASSWORD= +MYSQL_HOST= +MYSQL_PORT= +MYSQL_DATABASE= + +BOT_EMAIL= +BOT_USERNAME= +BOT_PASSWORD= + +ADMIN_EMAIL= +ADMIN_USERNAME= +ADMIN_PASSWORD= diff --git a/deploy/aktienbot/.env.bot b/deploy/aktienbot/.env.bot new file mode 100644 index 0000000..c64cd3c --- /dev/null +++ b/deploy/aktienbot/.env.bot @@ -0,0 +1,3 @@ +BOT_API_KEY= +NEWS_API_KEY= +SECRET_KEY= diff --git a/deploy/aktienbot/docker-compose.yml b/deploy/aktienbot/docker-compose.yml new file mode 100644 index 0000000..f72ab3b --- /dev/null +++ b/deploy/aktienbot/docker-compose.yml @@ -0,0 +1,62 @@ +version: '3.7' + +services: + aktienbot_fe: + image: registry.flokaiser.com/aktienbot/frontend + labels: + traefik.enable: 'true' + traefik.http.routers.aktienbot_fe.rule: Host(`gruppe1.testsites.info`) + traefik.http.routers.aktienbot_fe.middlewares: secHeaders@file + traefik.http.routers.aktienbot_fe.priority: 40 + traefik.http.routers.aktienbot_fe.tls: true + traefik.http.routers.aktienbot_fe.tls.certresolver: myresolver + + aktienbot_api: + image: registry.flokaiser.com/aktienbot/api + labels: + traefik.enable: 'true' + traefik.http.routers.aktienbot_api.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/api`) + traefik.http.routers.aktienbot_api.middlewares: secHeaders@file + traefik.http.routers.aktienbot_api.priority: 50 + traefik.http.routers.aktienbot_api.tls: true + traefik.http.routers.aktienbot_api.tls.certresolver: myresolver + depends_on: + - mariadb + env_file: + - ${PWD}/.env.api + + aktienbot_bot: + image: registry.flokaiser.com/aktienbot/bot + env_file: + - ${PWD}/.env.bot + + mariadb: + image: mariadb + volumes: + - mariadb_data:/var/lib/mysql + environment: + - MYSQL_ROOT_PASSWORD=sBvKtMY7ej9*dETatTtk#uRd5f*5wJYovfdDJDa& + + phpmyadmin: + image: phpmyadmin + environment: + - PMA_HOST=mariadb + - PMA_ABSOLUTE_URI=http://gruppe1.testsites.info/phpmyadmin/ + labels: + traefik.enable: true + traefik.http.routers.phpmyadmin.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/phpmyadmin`) + traefik.http.routers.phpmyadmin.middlewares: secHeaders@file + traefik.http.routers.phpmyadmin.priority: 50 + traefik.http.routers.phpmyadmin.middlewares: strip_phpmyadmin + traefik.http.routers.phpmyadmin.tls: true + traefik.http.routers.phpmyadmin.tls.certresolver: myresolver + + traefik.http.middlewares.strip_phpmyadmin.stripprefix.prefixes: /phpmyadmin + +networks: + default: + external: + name: net +volumes: + portainer_data: + mariadb_data: diff --git a/deploy/base/acme.json b/deploy/base/acme.json new file mode 100644 index 0000000..e69de29 diff --git a/deploy/base/docker-compose.yml b/deploy/base/docker-compose.yml new file mode 100644 index 0000000..6cd9531 --- /dev/null +++ b/deploy/base/docker-compose.yml @@ -0,0 +1,38 @@ +version: '3' + +services: + traefik: + image: traefik + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ${PWD}/traefik.toml:/etc/traefik/traefik.toml + - ${PWD}/traefik-dynamic.toml:/etc/traefik/traefik-dynamic.toml + - ${PWD}/acme.json:/etc/traefik/acme.json + - ${PWD}/access.log:/etc/traefik/access.log + + portainer: + image: portainer/portainer-ce + labels: + traefik.enable: true + traefik.http.routers.portainer.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/portainer`) + traefik.http.routers.portainer.priority: 50 + traefik.http.services.portainer.loadbalancer.server.port: 9000 + traefik.http.routers.portainer.middlewares: strip_portainer,secHeaders@file + traefik.http.routers.portainer.tls: true + traefik.http.routers.portainer.tls.certresolver: myresolver + + traefik.http.middlewares.strip_portainer.stripprefix.prefixes: /portainer + volumes: + - portainer_data:/data + - /var/run/docker.sock:/var/run/docker.sock + +networks: + default: + external: + name: net + +volumes: + portainer_data: diff --git a/deploy/base/traefik-dynamic.toml b/deploy/base/traefik-dynamic.toml new file mode 100644 index 0000000..731644f --- /dev/null +++ b/deploy/base/traefik-dynamic.toml @@ -0,0 +1,22 @@ +[tls.options] + [tls.options.default] + minVersion = "VersionTLS12" + cipherSuites = [ + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256" + ] + curvePreferences = [ "CurveP521", "CurveP384" ] + sniStrict = true + +[http.middlewares.secHeaders.headers] + browserXssFilter = true + contentTypeNosniff = true + frameDeny = true + stsIncludeSubdomains = true + stsPreload = true + stsSeconds = 31_536_000 + customFrameOptionsValue = "SAMEORIGIN" diff --git a/deploy/base/traefik.toml b/deploy/base/traefik.toml new file mode 100644 index 0000000..d9c0177 --- /dev/null +++ b/deploy/base/traefik.toml @@ -0,0 +1,44 @@ +[log] + level = "INFO" + +[accessLog] + filePath = "/etc/traefik/access.log" + +[entryPoints] + [entryPoints.web] + address = ":80" + + [entryPoints.web.forwardedHeaders] + insecure = true + + [entryPoints.web.http] + [entryPoints.web.http.redirections] + [entryPoints.web.http.redirections.entryPoint] + to = "web-secure" + scheme = "https" + + [entryPoints.web-secure] + address = ":443" + + [entryPoints.web-secure.forwardedHeaders] + insecure = true + + [entryPoints.websecure.http] + middlewares = ["secHeaders@file"] + +[api] + dashboard = true + insecure = true + +[providers.docker] + watch = true + exposedByDefault = false + +[providers.file] + filename = "/etc/traefik/traefik-dynamic.toml" + +[certificatesResolvers.myresolver.acme] + email = "inf20155@lehre.dhbw-stuttgart.de" + storage = "/etc/traefik/acme.json" + [certificatesResolvers.myresolver.acme.httpChallenge] + entryPoint = "web" \ No newline at end of file From cc70f8c47850fd6756983508c4ebf8e405b0fdda Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Mon, 4 Apr 2022 16:38:15 +0200 Subject: [PATCH 136/263] share handler in api_handler --- telegram_bot/api_handling/api_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index 8c14833..bc5ef79 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -248,4 +248,6 @@ if __name__ == "__main__": print(handler.token) keywords = handler.get_user_keywords(user_id = 1709356058) #user_id is currently mine (Linus) print(keywords) + shares = handler.get_user_portfolio(user_id = 1709356058) + print(shares) sys.exit(1) \ No newline at end of file From cb850aa6bc89385e3d68de3c0f3de6babcd60786 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 16:39:35 +0200 Subject: [PATCH 137/263] Fixed docker-compose.yml --- deploy/aktienbot/docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/aktienbot/docker-compose.yml b/deploy/aktienbot/docker-compose.yml index f72ab3b..a485892 100644 --- a/deploy/aktienbot/docker-compose.yml +++ b/deploy/aktienbot/docker-compose.yml @@ -41,13 +41,12 @@ services: image: phpmyadmin environment: - PMA_HOST=mariadb - - PMA_ABSOLUTE_URI=http://gruppe1.testsites.info/phpmyadmin/ + - PMA_ABSOLUTE_URI=https://gruppe1.testsites.info/phpmyadmin/ labels: traefik.enable: true traefik.http.routers.phpmyadmin.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/phpmyadmin`) - traefik.http.routers.phpmyadmin.middlewares: secHeaders@file + traefik.http.routers.phpmyadmin.middlewares: secHeaders@file,strip_phpmyadmin traefik.http.routers.phpmyadmin.priority: 50 - traefik.http.routers.phpmyadmin.middlewares: strip_phpmyadmin traefik.http.routers.phpmyadmin.tls: true traefik.http.routers.phpmyadmin.tls.certresolver: myresolver @@ -57,6 +56,7 @@ networks: default: external: name: net + volumes: portainer_data: mariadb_data: From 0b8ae845ede4b1cb7f20b7fb6ad5c099409f8e07 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 4 Apr 2022 16:54:25 +0200 Subject: [PATCH 138/263] added max req params --- telegram_bot/api_handling/api_handler.py | 49 ++++++++++++++++++------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index bc5ef79..88810ce 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -74,15 +74,19 @@ class API_Handler: return None - def get_user_keywords(self, user_id): + def get_user_keywords(self, user_id, max_retries=10): """gets the keywords of the user Args: user_id (int): id of the user + max_retries (int): max retries for the request Returns: list: list of keywords """ + if max_retries <= 0: + return None + keywords = [] with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} @@ -95,7 +99,7 @@ class API_Handler: return keywords else: - return self.get_user_keywords(user_id) # might end in infinite loop!! + return self.get_user_keywords(user_id, max_retries-1) @@ -133,24 +137,32 @@ class API_Handler: return req.status_code - def get_user_shares(self, user_id): + def get_user_shares(self, user_id, max_retries=10): """gets the shares of the user Args: user_id (int): id of the user + max_retries (int): max retries for the request Returns: list: list of shares """ + if max_retries <= 0: + return None + with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} req = s.get(self.db_adress + "/shares", headers=headers) - shares_json = req.json()["data"] - shares = [] - for share in shares_json: - shares.append(share["symbol"]) + if(req.status_code == 200): + shares_json = req.json()["data"] + shares = [] + for share in shares_json: + shares.append(share["symbol"]) - return shares + return shares + + else: + return self.get_user_shares(user_id, max_retries-1) def set_share(self, user_id, symbol): @@ -185,15 +197,19 @@ class API_Handler: return req.status_code - def get_user_transactions(self, user_id): + def get_user_transactions(self, user_id, max_retries=10): """gets the transactions of the user Args: user_id (int): id of the user + max_retries (int): max retries for the request Returns: dict: dictionary of transactions """ + if max_retries <= 0: + return None + with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} req = s.get(self.db_adress + "/transactions", headers=headers) @@ -201,6 +217,8 @@ class API_Handler: if req.status_code == 200: transactions_dict = dict(req.json()["data"]) return transactions_dict + else: + return self.get_user_transactions(user_id, max_retries-1) def set_transaction(self, user_id, count, price, symbol, timestamp): @@ -223,20 +241,27 @@ class API_Handler: return req.status_code - def get_user_portfolio(self, user_id): + def get_user_portfolio(self, user_id, max_retries=10): """gets the portfolio of the user Args: user_id (int): id of the user + max_retries (int): max retries for the request Returns: dict: dictionary of portfolio """ + if max_retries <= 0: + return None + with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} req = s.get(self.db_adress + "/portfolio", headers=headers) - portfolio_dict = dict(req.json()["data"]) - return portfolio_dict + if req.status_code == 200: + portfolio_dict = dict(req.json()["data"]) + return portfolio_dict + else: + return self.get_user_portfolio(user_id, max_retries-1) From a2aed53bc89451f4d1259849f12e2b3d57b1b483 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 4 Apr 2022 17:33:54 +0200 Subject: [PATCH 139/263] api adress updated --- telegram_bot/bot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index b24599a..be23eb4 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -38,8 +38,8 @@ bot_version = "0.2.1" user_list = [] #create api handler -api_handler = API_Handler("https://aktienbot.flokaiser.com/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD"))) -print(api_handler.token) +api_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD"))) +print("Webserver Token: " + str(api_handler.token)) class User: # Currently saving users in this class to test functionality -> later database def __init__(self, p_user_id, p_user_name, p_chat_id): From 643c040b32cefdb180468d606b0e01ca0d7cc742 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 4 Apr 2022 18:57:59 +0200 Subject: [PATCH 140/263] updated and added functions --- telegram_bot/news/news_fetcher.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/telegram_bot/news/news_fetcher.py b/telegram_bot/news/news_fetcher.py index f913257..332dafd 100644 --- a/telegram_bot/news/news_fetcher.py +++ b/telegram_bot/news/news_fetcher.py @@ -36,7 +36,26 @@ def get_all_news_by_keyword(keyword, from_date="2000-01-01"): # hard coded will JSON/dict: dict containing articles """ top_headlines = newsapi.get_everything(q=keyword, sources=str_sources, language='en', from_param=from_date) - return top_headlines + if(top_headlines["status"] == "ok"): + return top_headlines + else: + return None + + +def get_top_news_by_keyword(keyword): + """get top news to keyword + Args: + keyword (String): keyword for search + + Returns: + JSON/dict: dict containing articles + """ + top_headlines = newsapi.get_top_headlines(q=keyword, sources=str_sources, language='en') + if(top_headlines["status"] == "ok"): + return top_headlines + else: + return None + def format_article(article): """format article for messaging (using markdown syntax) From 96e96b4612922f155278fc205a67a23f4546ae0f Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 4 Apr 2022 18:59:00 +0200 Subject: [PATCH 141/263] updated functions, added fail handling and messages --- telegram_bot/bot.py | 88 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 15 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index be23eb4..c50ad61 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -14,6 +14,7 @@ __license__ = "None" # API Documentation https://core.telegram.org/bots/api # Code examples https://github.com/eternnoir/pyTelegramBotAPI#getting-started +from ast import keyword import os import telebot @@ -45,8 +46,8 @@ class User: # Currently saving users in this class to test functionality -> late def __init__(self, p_user_id, p_user_name, p_chat_id): """ Initialize a new user - :type self: - :param self: for class + :type self: User + :param self: object of the class :type p_user_id: int :param p_user_id: telegram user id @@ -115,7 +116,7 @@ def send_welcome(message): :rtype: none """ - bot.reply_to(message, "/id or /auth for authentication. /update to get updates on your shares. /users to see all users. /news to get current use for your keywords. /share to get price of specific share. For further details see aktienbot.flokaiser.com") + bot.reply_to(message, "/id or /auth get your user id\n/update get updates on your shares.\n/users see all users.\n/news get top article for each keyword.\n/allnews get all news (last 7 days)\n/keywords get all your keywords\n/addkeyword add a keyword\n/removekeyword remove a keyword\n/share get price of specific share\n\n_For further details see https://gruppe1.testsites.info _", parse_mode='MARKDOWN') @bot.message_handler(commands=['users']) @@ -152,7 +153,7 @@ def send_id(message): :rtype: none """ - answer = 'Your ID/Authentication Code is: [' + str(message.from_user.id) + ']. Enter this code in the settings on aktienbot.flokaiser.com to get updates on your shares.' + answer = 'Your ID/Authentication Code is: [' + str(message.from_user.id) + ']. Enter this code in the settings on https://gruppe1.testsites.info to get updates on your shares.' bot.reply_to(message, answer) @@ -205,7 +206,7 @@ def send_update(message): my_update_message = f'Symbol: {my_share_symbol}\nPrice: {my_share_course}\nBought for: {my_share_buy_price}\n\ -Amount owned: {my_share_amount}\nWin/Lose: {(my_share_amount*my_share_course) - (my_share_amount*my_share_buy_price)}' + Amount owned: {my_share_amount}\nWin/Lose: {(my_share_amount*my_share_course) - (my_share_amount*my_share_buy_price)}' bot.send_message(chat_id=user_id, text=my_update_message) @@ -232,12 +233,12 @@ def send_share_price(message): bot.reply_to(message, str_share_price) -@bot.message_handler(commands=['news']) -def send_news(message): +@bot.message_handler(commands=['allnews']) +def send_all_news(message): """ Get news for keywords of user :type message: message object bot - :param message: message that was reacted to, in this case always containing '/news' + :param message: message that was reacted to, in this case always containing '/allnews' :raises: none @@ -246,13 +247,22 @@ def send_news(message): user_id = int(message.from_user.id) keywords = api_handler.get_user_keywords(user_id) + + if keywords == None: + bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure too connect your telegram id (/id) on https://gruppe1.testsites.info') + return + + if not keywords: + bot.send_message(chat_id=user_id, text='You have no keywords. Please add some keywords with /news') + return + keywords_search = ' OR '.join(keywords) print(keywords_search) now = dt.datetime.now().date() from_date = now - dt.timedelta(days=7) from_date_formatted = dt.datetime.strftime(from_date, '%Y-%m-%d') print(from_date_formatted) - news_list = news.get_all_news_by_keyword(keywords_search, from_date_formatted)["articles"][:5] + news_list = news.get_all_news_by_keyword(keywords_search, from_date_formatted)["articles"] if news_list: for article in news_list: @@ -262,6 +272,40 @@ def send_news(message): bot.send_message(chat_id=user_id, text='No news found for your keywords.') +@bot.message_handler(commands=['news']) +def send_news(message): + """ Get news for keywords of user + + :type message: message object bot + :param message: message that was reacted to, in this case always containing '/news' + + :raises: none + + :rtype: none + """ + user_id = int(message.from_user.id) + keywords = api_handler.get_user_keywords(user_id) + + if keywords == None: + bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure too connect your telegram id (/id) on https://gruppe1.testsites.info') + return + + if not keywords: + bot.send_message(chat_id=user_id, text='You have no keywords. Please add some keywords with /addkeyword') + return + + if keywords: + for keyword in keywords: + top_news = news.get_top_news_by_keyword(keyword)["articles"] + if top_news == None: + bot.send_message(chat_id=user_id, text='News Server did not respond correctly. Try again later.') + return + if not top_news: + bot.send_message(chat_id=user_id, text=f'No news found for keyword: *{keyword}*', parse_mode="MARKDOWN") + return + + formatted_article = news.format_article(top_news[0]) + bot.send_message(chat_id=user_id, text=f"_keyword: {keyword}_\n\n" + formatted_article, parse_mode="MARKDOWN") @bot.message_handler(commands=['addkeyword']) @@ -282,8 +326,11 @@ def store_keyword(message): user_id = int(message.from_user.id) print(str(user_id)) keyword = str(message.text).lower() - api_handler.set_keyword(user_id, keyword) - bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" added.') + status = api_handler.set_keyword(user_id, keyword) + if status == 200: + bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" added.') + else: + bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" could not be stored. (statuscode {status})') @bot.message_handler(commands=['removekeyword']) @@ -303,8 +350,11 @@ def remove_keyword(message): def remove_keyword_step(message): user_id = int(message.from_user.id) keyword = str(message.text).lower() - api_handler.delete_keyword(user_id, keyword) - bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" removed.') + status = api_handler.delete_keyword(user_id, keyword) + if status == 200: + bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" removed.') + else: + bot.send_message(chat_id=user_id, text=f'Failed deleting keyword "{keyword}". (statuscode {status})') @bot.message_handler(commands=['keywords']) @@ -319,10 +369,18 @@ def send_keywords(message): """ user_id = int(message.from_user.id) keywords = api_handler.get_user_keywords(user_id) - bot.send_message(chat_id=user_id, text=f'Your keywords are: {keywords}') + if keywords == None: + bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure too connect your telegram id (/id) on https://gruppe1.testsites.info') + return + if not keywords: + bot.send_message(chat_id=user_id, text='No keywords set for this account. Add keywords by using /addkeyword') + return + else: + keywords_str = ', '.join(keywords) + bot.send_message(chat_id=user_id, text=f'Your keywords are: _{keywords_str}_', parse_mode="MARKDOWN") -@bot.message_handler(func=lambda message: True) # Returning that command is unkown for any other statement +@bot.message_handler(func=lambda message: True) # Returning that command is unknown for any other statement def echo_all(message): """ Tell that command is not known if it is no known command From 3711331338b61bd954e93ad462c44deeda482c3c Mon Sep 17 00:00:00 2001 From: Rripped <75929322+Rripped@users.noreply.github.com> Date: Mon, 4 Apr 2022 19:01:23 +0200 Subject: [PATCH 142/263] Update README.md --- documentation/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/README.md b/documentation/README.md index 3bdec2f..80c8665 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -1,7 +1,7 @@ # Dokumentation ## Swagger Documentation -Visit https://aktienbot.flokaiser.com/api/docs +Visit https://gruppe1.testsites.info/api/docs ## API - `api/openapi.json` From 20261a432064723786cc4d71e454da91c645f362 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 23:03:16 +0200 Subject: [PATCH 143/263] Updated frontend rule --- deploy/aktienbot/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/aktienbot/docker-compose.yml b/deploy/aktienbot/docker-compose.yml index a485892..46aee48 100644 --- a/deploy/aktienbot/docker-compose.yml +++ b/deploy/aktienbot/docker-compose.yml @@ -5,7 +5,7 @@ services: image: registry.flokaiser.com/aktienbot/frontend labels: traefik.enable: 'true' - traefik.http.routers.aktienbot_fe.rule: Host(`gruppe1.testsites.info`) + traefik.http.routers.aktienbot_fe.rule: Host(`gruppe1.testsites.info`) && !PathPrefix(`/api`) && !PathPrefix(`/phpmyadmin`) && !PathPrefix(`/portainer`) traefik.http.routers.aktienbot_fe.middlewares: secHeaders@file traefik.http.routers.aktienbot_fe.priority: 40 traefik.http.routers.aktienbot_fe.tls: true From 29fea56bbf651d5c91d4495d7b4155ede810e016 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 4 Apr 2022 23:07:26 +0200 Subject: [PATCH 144/263] Try to fix database problems --- api/app/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/app/__init__.py b/api/app/__init__.py index c62e70c..89e8bb5 100644 --- a/api/app/__init__.py +++ b/api/app/__init__.py @@ -23,8 +23,6 @@ def create_app(config_filename=None): CORS(application, resources={r"*": {"origins": "*"}}) - application.app_context().push() - db.init_app(application) # api blueprints From 82087a46c096a1a81dd72ea46f309781b81623c8 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 5 Apr 2022 00:27:22 +0200 Subject: [PATCH 145/263] Added goaccess to monitor traefik --- deploy/base/docker-compose.yml | 35 ++++++++++++++++++++++++++++++++++ deploy/base/goaccess.conf | 5 +++++ 2 files changed, 40 insertions(+) create mode 100644 deploy/base/goaccess.conf diff --git a/deploy/base/docker-compose.yml b/deploy/base/docker-compose.yml index 6cd9531..0c2897f 100644 --- a/deploy/base/docker-compose.yml +++ b/deploy/base/docker-compose.yml @@ -13,6 +13,39 @@ services: - ${PWD}/acme.json:/etc/traefik/acme.json - ${PWD}/access.log:/etc/traefik/access.log + goaccess: + image: allinurl/goaccess + command: + - --no-global-config + - --config-file=/srv/data/goaccess.conf + - --num-tests=0 + volumes: + - ${PWD}/access.log:/srv/logs/access.log:ro + - ${PWD}/goaccess.conf:/srv/data/goaccess.conf + - goaccess_data:/srv/data + - goaccess_report:/srv/report + labels: + traefik.enable: true + traefik.http.routers.goaccess.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/goaccess/ws`) + traefik.http.routers.goaccess.priority: 55 + traefik.http.routers.goaccess.middlewares: strip_goaccess,secHeaders@file + traefik.http.routers.goaccess.tls: true + traefik.http.routers.goaccess.tls.certresolver: myresolver + + nginx: + image: nginx + volumes: + - goaccess_report:/usr/share/nginx/html + labels: + traefik.enable: true + traefik.http.routers.goaccess_web.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/goaccess`) + traefik.http.routers.goaccess_web.priority: 50 + traefik.http.routers.goaccess_web.middlewares: strip_goaccess,secHeaders@file + traefik.http.routers.goaccess_web.tls: true + traefik.http.routers.goaccess_web.tls.certresolver: myresolver + + traefik.http.middlewares.strip_goaccess.stripprefix.prefixes: /goaccess + portainer: image: portainer/portainer-ce labels: @@ -36,3 +69,5 @@ networks: volumes: portainer_data: + goaccess_report: + goaccess_data: \ No newline at end of file diff --git a/deploy/base/goaccess.conf b/deploy/base/goaccess.conf new file mode 100644 index 0000000..e23848e --- /dev/null +++ b/deploy/base/goaccess.conf @@ -0,0 +1,5 @@ +log-format COMMON +log-file /srv/logs/access.log +output /srv/report/index.html +real-time-html true +ws-url wss://gruppe1.testsites.info:443/goaccess/ws From d63a39b8522e91443a169974cfea0affbe5b89f0 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 5 Apr 2022 08:34:11 +0200 Subject: [PATCH 146/263] Update pipeline --- .woodpecker/pipeline.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index d202d96..86eefd1 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -4,6 +4,7 @@ pipeline: commands: - echo -n "${CI_COMMIT_BRANCH//\//-}-${CI_COMMIT_SHA:0:8}, latest" > .tags when: + path: [ "frontend/**", "telegram_bot/**", "api/**" ] event: push @@ -11,7 +12,7 @@ pipeline: build_api: image: woodpeckerci/plugin-docker-buildx settings: - repo: + repo: from_secret: repo_api username: from_secret: username @@ -30,7 +31,7 @@ pipeline: build_bot: image: woodpeckerci/plugin-docker-buildx settings: - repo: + repo: from_secret: repo_bot username: from_secret: username @@ -49,7 +50,7 @@ pipeline: build_frontend: image: woodpeckerci/plugin-docker-buildx settings: - repo: + repo: from_secret: repo_frontend username: from_secret: username @@ -80,6 +81,7 @@ pipeline: - docker-compose pull - docker-compose -p "aktienbot" up -d when: + path: [ "frontend/**", "telegram_bot/**", "api/**" ] event: push - + branches: main From 6fd69eb4d53fff814ca0aad84fd35d5c4bfbfd88 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Tue, 5 Apr 2022 08:39:46 +0200 Subject: [PATCH 147/263] Update README.md --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 022b2bc..30d6595 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,13 @@ WebEngineering2 Projekt: Aktien und News Bot für Telegram ## Dokumentation -> README.md in /documentation + +## Nützliche Tools +- Portainer (https://gruppe1.testsites.info/portainer/) \ + *Container Management System* +- phpMyAdmin (https://gruppe1.testsites.info/phpmyadmin/) \ + *Administration von MySQL-Datenbanken* +- goaccess (https://gruppe1.testsites.info/goaccess/) \ + *Webanalyseanwendung* +- Uptimekuma (https://uptimekuma.flokaiser.com/status/aktienbot) \ + *Monitoring* From 96982f273deb80330c6d9eeb1ad2fbbe1498b798 Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Tue, 5 Apr 2022 08:41:16 +0200 Subject: [PATCH 148/263] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 30d6595..8e1916a 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,5 @@ WebEngineering2 Projekt: Aktien und News Bot für Telegram *Webanalyseanwendung* - Uptimekuma (https://uptimekuma.flokaiser.com/status/aktienbot) \ *Monitoring* +- Woodpecker (https://woodpecker.flokaiser.com/WebEngineering2/TelegramAktienBot) \ + *Continuous Integration platform* From 69075e590e875c4df5bb46614d95203d56b81cac Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 5 Apr 2022 08:45:30 +0200 Subject: [PATCH 149/263] Fixed pipeline condition --- .woodpecker/pipeline.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 86eefd1..7a0d6a1 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -80,8 +80,8 @@ pipeline: - cd /root/docker/aktienbot - docker-compose pull - docker-compose -p "aktienbot" up -d - when: - path: [ "frontend/**", "telegram_bot/**", "api/**" ] - event: push + when: + path: [ "frontend/**", "telegram_bot/**", "api/**" ] + event: push branches: main From 9d10b1a048f42df4073b0df79087be0d121d41d3 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 5 Apr 2022 10:07:03 +0200 Subject: [PATCH 150/263] New file for regularly sending bot updates --- telegram_bot/bot_updates.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 telegram_bot/bot_updates.py diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py new file mode 100644 index 0000000..aaacd7b --- /dev/null +++ b/telegram_bot/bot_updates.py @@ -0,0 +1,7 @@ +""" +script for regularly sending updates on shares and news based on user interval +""" +__author__ = "Florian Kellermann, Linus Eickhoff" +__date__ = "05.04.2022" +__version__ = "0.0.1" +__license__ = "None" \ No newline at end of file From 835beed17dba2f8fc92484e94fac2827e148dc28 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 5 Apr 2022 10:51:09 +0200 Subject: [PATCH 151/263] Added cron field to database --- api/app/blueprints/telegram.py | 9 +-- api/app/blueprints/user.py | 57 ++++++++++----- api/app/helper_functions.py | 71 ++++++++++-------- api/app/models.py | 6 +- api/app/schema.py | 4 + api/tests/functional/helper_functions.py | 2 +- api/tests/functional/test_user.py | 93 +++++++++++++++++++++++- api/tests/unit/test_helper_functions.py | 7 ++ api/tests/unit/test_models.py | 8 +- api/tests/unit/test_user.py | 9 +++ 10 files changed, 203 insertions(+), 63 deletions(-) diff --git a/api/app/blueprints/telegram.py b/api/app/blueprints/telegram.py index 5208d71..c96e923 100644 --- a/api/app/blueprints/telegram.py +++ b/api/app/blueprints/telegram.py @@ -1,12 +1,10 @@ import os from apiflask import APIBlueprint, abort - -from app.db import database as db -from app.helper_functions import make_response, get_email_or_abort_401 from app.auth import auth +from app.db import database as db +from app.helper_functions import make_response, get_email_or_abort_401, get_user from app.schema import TelegramIdSchema, UsersSchema -from app.models import User telegram_blueprint = APIBlueprint('telegram', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -23,7 +21,8 @@ def add_keyword(data): if not check_if_telegram_user_id_data_exists(data): abort(400, message="User ID missing") - query_user = db.session.query(User).filter_by(email=email).first() + query_user = get_user(email) + query_user.telegram_user_id = data['telegram_user_id'] db.session.commit() diff --git a/api/app/blueprints/user.py b/api/app/blueprints/user.py index 771bd60..a570883 100644 --- a/api/app/blueprints/user.py +++ b/api/app/blueprints/user.py @@ -1,15 +1,14 @@ import datetime import os -from flask import current_app import jwt from apiflask import APIBlueprint, abort - -from app.db import database as db -from app.helper_functions import check_password, hash_password, abort_if_no_admin, make_response, get_email_or_abort_401 -from app.models import User -from app.schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema, RegisterDataSchema, UpdateUserDataSchema from app.auth import auth +from app.db import database as db +from app.helper_functions import check_password, hash_password, abort_if_no_admin, make_response, get_email_or_abort_401, get_user +from app.models import User +from app.schema import UsersSchema, TokenSchema, LoginDataSchema, AdminDataSchema, DeleteUserSchema, RegisterDataSchema, UpdateUserDataSchema, CronDataSchema +from flask import current_app users_blueprint = APIBlueprint('users', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -36,9 +35,9 @@ def users(): def user(): email = get_email_or_abort_401() - res = db.session.query(User).filter_by(email=email).first().as_dict() + query_user = get_user(email) - return make_response(res, 200, "Successfully received current user data") + return make_response(query_user.as_dict(), 200, "Successfully received current user data") @users_blueprint.route('/user/login', methods=['POST']) @@ -55,10 +54,7 @@ def login(data): email = data['email'] password = data['password'] - query_user = db.session.query(User).filter_by(email=email).first() - - if query_user is None: # email doesn't exist - abort(500, message="Unable to login") + query_user = get_user(email) if not check_password(query_user.password, password.encode("utf-8")): # Password incorrect abort(500, message="Unable to login") @@ -98,7 +94,8 @@ def register(data): email=email, username=username, password=hash_password(password), - admin=False + admin=False, + cron="0 8 * * *" ) db.session.add(new_user) db.session.commit() @@ -114,7 +111,7 @@ def register(data): def update_user(data): email = get_email_or_abort_401() - query_user = db.session.query(User).filter_by(email=email).first() + query_user = get_user(email) if check_if_password_data_exists(data): query_user.password = hash_password(data['password']) @@ -144,10 +141,7 @@ def set_admin(data): email = data['email'] admin = data['admin'] - query_user = db.session.query(User).filter_by(email=email).first() - - if query_user is None: # Username doesn't exist - abort(500, message="Unable to update user") + query_user = get_user(email) query_user.admin = admin db.session.commit() @@ -155,6 +149,23 @@ def set_admin(data): return make_response({}, 200, "Successfully updated users admin rights") +@users_blueprint.route('/user/setCron', methods=['PUT']) +@users_blueprint.output({}, 200) +@users_blueprint.input(schema=CronDataSchema) +@users_blueprint.auth_required(auth) +@users_blueprint.doc(summary="Set update cron", description="Set update cron of specified user") +def set_cron(data): + email = get_email_or_abort_401() + + if not check_if_cron_data_exists(data): + abort(400, "Cron data missing") + + get_user(email).cron = data['cron'] + db.session.commit() + + return make_response({}, 200, "Successfully updated users cron") + + @users_blueprint.route('/user', methods=['DELETE']) @users_blueprint.output({}, 200) @users_blueprint.input(schema=DeleteUserSchema) @@ -216,3 +227,13 @@ def check_if_admin_data_exists(data): return False return True + + +def check_if_cron_data_exists(data): + if "cron" not in data: + return False + + if data['cron'] == "" or data['cron'] is None: + return False + + return True diff --git a/api/app/helper_functions.py b/api/app/helper_functions.py index 516d0e7..ee90298 100644 --- a/api/app/helper_functions.py +++ b/api/app/helper_functions.py @@ -1,12 +1,10 @@ -from flask import current_app - import bcrypt import jwt from apiflask import abort -from flask import request, jsonify - from app.db import database as db from app.models import User +from flask import current_app +from flask import request, jsonify def hash_password(password): @@ -17,45 +15,47 @@ def check_password(hashed_password, user_password): return bcrypt.checkpw(user_password, hashed_password) -def get_email_from_token_data(): - if 'Authorization' in request.headers: - token = request.headers['Authorization'].split(" ") +def get_email_from_token_data(token): + if token is None or len(token) < 2: + return None + else: + token = token[1] - if len(token) < 2: - return None - else: - token = token[1] + if token is not None: + if ':' in token: # Maybe bot token, check if token valid and return username after ":" then + telegram_user_id = token.split(":")[1] + token = token.split(":")[0] - if token is not None: - if ':' in token: # Maybe bot token, check if token valid and return username after ":" then - telegram_user_id = token.split(":")[1] - token = token.split(":")[0] + try: + if jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] == current_app.config['BOT_EMAIL']: + res = db.session.query(User).filter_by(telegram_user_id=telegram_user_id).first() - try: - if jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] == current_app.config['BOT_EMAIL']: - res = db.session.query(User).filter_by(telegram_user_id=telegram_user_id).first() - - if res is not None: - return res.as_dict()['email'] - else: - return None + if res is not None: + return res.as_dict()['email'] else: return None - except jwt.PyJWTError: + else: return None + except jwt.PyJWTError: + return None - else: # "Normal" token, extract username from token - try: - return jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] - except jwt.PyJWTError: - return None + else: # "Normal" token, extract username from token + try: + return jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] + except jwt.PyJWTError: + return None - return None + +def get_token(): + if 'Authorization' in request.headers: + return request.headers['Authorization'].split(" ") + else: + return None def get_email_or_abort_401(): # get username from jwt token - email = get_email_from_token_data() + email = get_email_from_token_data(get_token()) if email is None: # If token not provided or invalid -> return 401 code abort(401, message="Unable to login") @@ -76,3 +76,12 @@ def is_user_admin(): def make_response(data, status=200, text=""): return jsonify({"status": status, "text": text, "data": data}) + + +def get_user(email): + query_user = db.session.query(User).filter_by(email=email).first() + + if query_user is None: # Username doesn't exist + abort(500, message="Can't find user") + + return query_user diff --git a/api/app/models.py b/api/app/models.py index 6c06c7b..14b42ac 100644 --- a/api/app/models.py +++ b/api/app/models.py @@ -7,14 +7,16 @@ class User(db.Model): password = db.Column('password', db.BINARY(60), nullable=False) username = db.Column('username', db.String(255), nullable=False, server_default='') telegram_user_id = db.Column('telegram_user_id', db.String(255), nullable=True, server_default='') - admin = db.Column('admin', db.Boolean(), server_default='0') + admin = db.Column('admin', db.Boolean(), server_default='0') # 0 = False, 1 = True + cron = db.Column('cron', db.String(20), server_default='0 8 * * *', nullable=False) def as_dict(self): return { "email": self.email, "username": self.username, "telegram_user_id": self.telegram_user_id, - "admin": self.admin + "admin": self.admin, + "cron": self.cron } diff --git a/api/app/schema.py b/api/app/schema.py index f425825..834ada3 100644 --- a/api/app/schema.py +++ b/api/app/schema.py @@ -23,6 +23,10 @@ class AdminDataSchema(Schema): admin = Boolean() +class CronDataSchema(Schema): + cron = String() + + class TokenSchema(Schema): token = String() diff --git a/api/tests/functional/helper_functions.py b/api/tests/functional/helper_functions.py index 9e68226..2b88442 100644 --- a/api/tests/functional/helper_functions.py +++ b/api/tests/functional/helper_functions.py @@ -8,4 +8,4 @@ def get_token(test_client, email, password): if "token" in json.loads(response.data)["data"]: return json.loads(response.data)["data"]["token"] - return "" \ No newline at end of file + return "" diff --git a/api/tests/functional/test_user.py b/api/tests/functional/test_user.py index 8b57338..80fdaec 100644 --- a/api/tests/functional/test_user.py +++ b/api/tests/functional/test_user.py @@ -2,6 +2,7 @@ This file (test_user.py) contains the functional tests for the `users` blueprint. """ import json + from tests.functional.helper_functions import get_token @@ -35,7 +36,7 @@ def test_login_user_not_exist(test_client, init_database): """ response = test_client.post('/api/user/login', data=json.dumps(dict(email="notexistinguser@example.com", password="password")), content_type='application/json') assert response.status_code == 500 - assert b'Unable to login' in response.data + assert b'Can\'t find user' in response.data def test_login_email_missing(test_client, init_database): @@ -407,7 +408,7 @@ def test_set_admin_admin1_logged_in_user_not_exist(test_client, init_database): headers={"Authorization": "Bearer {}".format(get_token(test_client, "admin1@example.com", "admin1"))}, content_type='application/json') assert response.status_code == 500 - assert b'Unable to update user' in response.data + assert b'Can\'t find user' in response.data def test_set_admin_admin1_logged_in_email_missing(test_client, init_database): @@ -466,6 +467,94 @@ def test_set_admin_admin1_logged_in_admin_empty(test_client, init_database): assert b'Field may not be null' in response.data +def test_set_cron_user_not_logged_in(test_client, init_database): + """ + Test PUT '/api/user/setCron' + + User is not logged in + """ + response = test_client.put('/api/user/setCron') + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + +def test_set_cron_user1_logged_in(test_client, init_database): + """ + Test PUT '/api/user/setCron' + + User1 is logged in + """ + response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="* * * * *")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully updated users cron' in response.data + + +def test_set_empty_cron_user1_logged_in(test_client, init_database): + """ + Test PUT '/api/user/setCron' + + User1 is logged in + Interval is empty + """ + response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password"))}, + content_type='application/json') + assert response.status_code == 400 + + +def test_set_cron_bot_logged_in_user_exists(test_client, init_database): + """ + Test PUT '/api/user/setCron' + + Bot1 is logged in and requests user 12345678 + """ + response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="* * * * *")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "bot1@example.com", "bot1") + ":12345678")}, + content_type='application/json') + assert response.status_code == 200 + assert b'Successfully updated users cron' in response.data + + +def test_set_cron_bot_logged_in_user_not_exists(test_client, init_database): + """ + Test PUT '/api/user/setCron' + + Bot1 is logged in and requests user 1234 (not existing) + """ + response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="* * * * *")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "bot1@example.com", "bot1") + ":1234")}, + content_type='application/json') + assert response.status_code == 401 + assert b'Unable to login' in response.data + + +def test_set_cron_user1_logged_in_but_no_bot(test_client, init_database): + """ + Test PUT '/api/user/setCron' + + User1 is logged in and requests user 1234 (not existing) + Fails because user1 is not a bot + """ + response = test_client.put('/api/user/setCron', data=json.dumps(dict(cron="* * * * *")), + headers={"Authorization": "Bearer {}".format(get_token(test_client, "user1@example.com", "password") + ":12345678")}, + content_type='application/json') + assert response.status_code == 401 + assert b'Unable to login' in response.data + + +def test_set_cron_invalid_token(test_client, init_database): + """ + Test PUT '/api/user/setCron' + + Invalid Bearer token + """ + response = test_client.put('/api/user/setCron', headers={"Authorization": "Bearer {}".format("invalidtoken:12345678")}) + assert response.status_code == 401 + assert b'Unauthorized' in response.data + + def test_get_users_not_logged_in(test_client, init_database): """ Test GET '/api/users' diff --git a/api/tests/unit/test_helper_functions.py b/api/tests/unit/test_helper_functions.py index 5cf054b..ef84fc4 100644 --- a/api/tests/unit/test_helper_functions.py +++ b/api/tests/unit/test_helper_functions.py @@ -18,3 +18,10 @@ def test_check_password(): hashed = hash_password("password") assert check_password(hashed, "password".encode("utf-8")) is True assert check_password(hashed, "password1".encode("utf-8")) is False + + +def test_get_email_from_token(): + """ + Test get_email_from_token function + """ + assert get_email_from_token_data(None) is None diff --git a/api/tests/unit/test_models.py b/api/tests/unit/test_models.py index e8a336a..690b58a 100644 --- a/api/tests/unit/test_models.py +++ b/api/tests/unit/test_models.py @@ -1,9 +1,8 @@ """ This file (test_models.py) contains the unit tests for the models.py file. """ -from app.models import User, Transaction, Keyword, Share - from app.helper_functions import hash_password +from app.models import User, Transaction, Keyword, Share def test_new_user(): @@ -16,14 +15,15 @@ def test_new_user(): email="user@example.com", username="user", password=hash_password("password"), - admin=False + admin=False, + cron="0 8 * * *", ) assert user.email == 'user@example.com' assert user.password != 'password' assert user.username == "user" assert user.telegram_user_id is None assert user.admin is False - assert user.as_dict() == {'email': 'user@example.com', 'username': 'user', 'telegram_user_id': None, 'admin': False} + assert user.as_dict() == {'cron': '0 8 * * *', 'email': 'user@example.com', 'username': 'user', 'telegram_user_id': None, 'admin': False} def test_new_user_with_fixture(new_user): diff --git a/api/tests/unit/test_user.py b/api/tests/unit/test_user.py index 91ad580..eb5706d 100644 --- a/api/tests/unit/test_user.py +++ b/api/tests/unit/test_user.py @@ -38,3 +38,12 @@ def test_check_if_admin_data_exists(): assert check_if_admin_data_exists(dict(admin=True)) is True assert check_if_admin_data_exists(dict(admin=None)) is False assert check_if_admin_data_exists(dict()) is False + + +def test_check_if_cron_data_exists(): + """ + Test check_if_cron_data_exists function + """ + assert check_if_cron_data_exists(dict(cron="* * * * *")) is True + assert check_if_cron_data_exists(dict(cron="")) is False + assert check_if_cron_data_exists(dict()) is False From 7f23c5f8a7f25901839500eece87a30553c27adc Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 5 Apr 2022 11:26:34 +0200 Subject: [PATCH 152/263] Added goaccess auth --- deploy/base/docker-compose.yml | 6 ++++-- deploy/base/users | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 deploy/base/users diff --git a/deploy/base/docker-compose.yml b/deploy/base/docker-compose.yml index 0c2897f..0782888 100644 --- a/deploy/base/docker-compose.yml +++ b/deploy/base/docker-compose.yml @@ -12,6 +12,7 @@ services: - ${PWD}/traefik-dynamic.toml:/etc/traefik/traefik-dynamic.toml - ${PWD}/acme.json:/etc/traefik/acme.json - ${PWD}/access.log:/etc/traefik/access.log + - ${PWD}/users:/etc/traefik/users goaccess: image: allinurl/goaccess @@ -28,7 +29,7 @@ services: traefik.enable: true traefik.http.routers.goaccess.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/goaccess/ws`) traefik.http.routers.goaccess.priority: 55 - traefik.http.routers.goaccess.middlewares: strip_goaccess,secHeaders@file + traefik.http.routers.goaccess.middlewares: strip_goaccess,goaccess_auth,secHeaders@file traefik.http.routers.goaccess.tls: true traefik.http.routers.goaccess.tls.certresolver: myresolver @@ -40,11 +41,12 @@ services: traefik.enable: true traefik.http.routers.goaccess_web.rule: Host(`gruppe1.testsites.info`) && PathPrefix(`/goaccess`) traefik.http.routers.goaccess_web.priority: 50 - traefik.http.routers.goaccess_web.middlewares: strip_goaccess,secHeaders@file + traefik.http.routers.goaccess_web.middlewares: strip_goaccess,goaccess_auth,secHeaders@file traefik.http.routers.goaccess_web.tls: true traefik.http.routers.goaccess_web.tls.certresolver: myresolver traefik.http.middlewares.strip_goaccess.stripprefix.prefixes: /goaccess + traefik.http.middlewares.goaccess_auth.basicauth.usersfile: /etc/traefik/users portainer: image: portainer/portainer-ce diff --git a/deploy/base/users b/deploy/base/users new file mode 100644 index 0000000..03bcafb --- /dev/null +++ b/deploy/base/users @@ -0,0 +1 @@ +# Create user by using ```htpasswd -nb user password``` and append output to this file. \ No newline at end of file From 852dedfc0ce9593d961aadef44d23a25bf7cbdf9 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 5 Apr 2022 12:38:06 +0200 Subject: [PATCH 153/263] Script to generate random transactions --- api/generate_sample_transactions.py | 25 +++++++++++++++++++++++++ api/requirements.txt | 3 ++- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 api/generate_sample_transactions.py diff --git a/api/generate_sample_transactions.py b/api/generate_sample_transactions.py new file mode 100644 index 0000000..adfbec4 --- /dev/null +++ b/api/generate_sample_transactions.py @@ -0,0 +1,25 @@ +import random + +import faker +import requests + +url = 'http://127.0.0.1:5000/api' +username = '' +password = '' + +shares = ["TWTR", "GOOG", "AAPL", "MSFT", "AMZN", "FB", "NFLX", "TSLA", "BABA", "BA", "BAC", "C", "CAT", "CSCO", "CVX", "DIS", "DOW", "DUK", "GE", "HD", "IBM" "INTC", "JNJ", "JPM", "KO", + "MCD", "MMM", "MRK", "NKE", "PFE", "PG", "T", "UNH", "UTX", "V", "VZ", "WMT", "XOM", "YHOO", "ZTS"] + +fake = faker.Faker() + +token = requests.post(url + '/user/login', json={"email": username, "password": password}).json()['data']['token'] + +for i in range(1, 1000): + payload = { + "count": random.randint(1, 100), + "price": random.random() * 100, + "symbol": shares[random.randint(0, len(shares) - 1)], + "time": fake.date_time().isoformat() + ".000Z" + } + + response = requests.post(url + '/transaction', json=payload, headers={'Authorization': 'Bearer ' + token}) diff --git a/api/requirements.txt b/api/requirements.txt index 2803098..6edbe0f 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -10,4 +10,5 @@ flask-cors==3.0.10 bcrypt==3.2.0 pytest~=7.1.1 pytest-cov -marshmallow~=3.15.0 \ No newline at end of file +marshmallow~=3.15.0 +faker~=4.0.0 \ No newline at end of file From e85bbacdde74f751a6101335540e83a6b1470fb2 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 5 Apr 2022 12:38:20 +0200 Subject: [PATCH 154/263] Example and description of crontab syntax --- telegram_bot/bot_updates.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index aaacd7b..08a3db8 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -4,4 +4,17 @@ script for regularly sending updates on shares and news based on user interval __author__ = "Florian Kellermann, Linus Eickhoff" __date__ = "05.04.2022" __version__ = "0.0.1" -__license__ = "None" \ No newline at end of file +__license__ = "None" + +''' +* * * * * code +┬ ┬ ┬ ┬ ┬ +│ │ │ │ │ +│ │ │ │ └──── weekday (0-7, Sunday is 0 or 7) +│ │ │ └────── Month (1-12) +│ │ └──────── Day (1-31) +│ └────────── Hour (0-23) +└──────────── Minute (0-59) + +example 0 8 * * * -> daily update at 8am +''' From 40d444f36c41fa9bc2265905c0e3620a01f04b41 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 5 Apr 2022 14:00:33 +0200 Subject: [PATCH 155/263] bot-updates got a send to user function and will be working with crontab --- telegram_bot/bot.py | 2 +- telegram_bot/bot_updates.py | 40 ++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index c50ad61..83582ba 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -419,7 +419,7 @@ def query_text(inline_query): def main_loop(): - """ Get Information about bot status every 3 seconds + """ Start bot :raises: none :rtype: none diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 08a3db8..88c3e86 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -6,11 +6,16 @@ __date__ = "05.04.2022" __version__ = "0.0.1" __license__ = "None" +from shares.share_fetcher import get_share_price +import time +from bot import bot +import sys + ''' * * * * * code ┬ ┬ ┬ ┬ ┬ │ │ │ │ │ -│ │ │ │ └──── weekday (0-7, Sunday is 0 or 7) +│ │ │ │ └──── weekday (0->Monday, 7->Sunday) │ │ │ └────── Month (1-12) │ │ └──────── Day (1-31) │ └────────── Hour (0-23) @@ -18,3 +23,36 @@ __license__ = "None" example 0 8 * * * -> daily update at 8am ''' + +def main_loop(): + """ main loop for regularly sending updates + :raises: none + + :rtype: none + """ + + +def send_to_user(pText, pUser_id = 1770205310): + + """ Send message to user + :type pText: string + :param pText: Text to send to user + + :type pUser_id: int + :param pUser_id: user to send to. per default me (Florian Kellermann) + + :raises: none + + :rtype: none + """ + bot.send_message(chat_id=pUser_id, text=pText) + + +if __name__ == "__main__": + print('This script shall not be run directly. Starting main_loop for debugging purposes.') + try: + main_loop() + sys.exit(-1) + except KeyboardInterrupt: + print("Ending") + sys.exit(-1) \ No newline at end of file From 2c0bbe31111cd4c6e9a0ab316346add54cc8ddcc Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 5 Apr 2022 14:12:12 +0200 Subject: [PATCH 156/263] Work in progress, need some communication first. Working on bot_updates --- telegram_bot/bot_updates.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 88c3e86..d5f351b 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -24,6 +24,9 @@ import sys example 0 8 * * * -> daily update at 8am ''' +user_ids = [] +user_crontab = [] + def main_loop(): """ main loop for regularly sending updates :raises: none @@ -31,6 +34,9 @@ def main_loop(): :rtype: none """ + current_time = time.ctime() + send_to_user(current_time) + def send_to_user(pText, pUser_id = 1770205310): From 7b99be963311696451a954938062a40ded09ebea Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 5 Apr 2022 15:07:06 +0200 Subject: [PATCH 157/263] Updating contrab based on time is working, need api calls --- telegram_bot/bot_updates.py | 51 +++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index d5f351b..e1fe445 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -8,8 +8,10 @@ __license__ = "None" from shares.share_fetcher import get_share_price import time +import datetime from bot import bot import sys +from multiprocessing import Process ''' * * * * * code @@ -34,8 +36,53 @@ def main_loop(): :rtype: none """ - current_time = time.ctime() - send_to_user(current_time) + current_time_datetime = datetime.datetime.now() + + print(time.ctime()) # Debug + + p1 = Process(target= update_crontab, args=(current_time_datetime, )) #Test threading instead of multiprocessing + p1.start() + p3 = Process(target= update_based_on_crontab, args=(current_time_datetime, ) ) + p3.daemon = True + p3.start() + p1.join() + p3.terminate() + p1.terminate() + + +def update_crontab(pCurrent_Time): + """ Updating crontab lists every hour + :type pCurrent_Time: time when starting crontab update + :param pCurrent_Time: datetime + + :raises: none + + :rtype: none + """ + + # Update user info now + + print('in update_crontab') + + user_ids.clear() # Example for me (Florian Kellermann) + user_crontab.clear() + user_ids.append(1770205310) + user_crontab.append("0 8 * * *") + + while True: + time_difference = datetime.datetime.now() - pCurrent_Time + if float(str(time_difference).split(':')[0]) >=1: + update_crontab(datetime.datetime.now()) + + +def update_based_on_crontab(pCurrent_Time): + current_time_ctime = time.ctime() + for i in range(len(user_crontab)): + print('tbd') + + + time.sleep(60) + def send_to_user(pText, pUser_id = 1770205310): From 695b7593be8df3d744c98beb73633d94537d3ef6 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 5 Apr 2022 15:08:57 +0200 Subject: [PATCH 158/263] Save symbol price in database --- api/.env.example | 3 + api/app/__init__.py | 2 + api/app/blueprints/share_price.py | 88 +++++++++++++++++++++++++++++ api/app/blueprints/transactions.py | 4 +- api/app/models.py | 11 ++++ api/app/schema.py | 6 ++ api/generate_sample_transactions.py | 6 +- api/load_share_price.py | 33 +++++++++++ api/requirements.txt | 4 +- 9 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 api/app/blueprints/share_price.py create mode 100644 api/load_share_price.py diff --git a/api/.env.example b/api/.env.example index 25dc0bf..2d183f9 100644 --- a/api/.env.example +++ b/api/.env.example @@ -16,3 +16,6 @@ BOT_PASSWORD= ADMIN_EMAIL= ADMIN_USERNAME= ADMIN_PASSWORD= + +# API URL (used for load_share_price.py and generate_sample_transactions.py) +API_URL= \ No newline at end of file diff --git a/api/app/__init__.py b/api/app/__init__.py index 89e8bb5..8040125 100644 --- a/api/app/__init__.py +++ b/api/app/__init__.py @@ -7,6 +7,7 @@ from flask_cors import CORS from app.blueprints.keyword import keyword_blueprint from app.blueprints.portfolio import portfolio_blueprint from app.blueprints.shares import shares_blueprint +from app.blueprints.share_price import share_price_blueprint from app.blueprints.transactions import transaction_blueprint from app.blueprints.telegram import telegram_blueprint from app.blueprints.user import users_blueprint @@ -28,6 +29,7 @@ def create_app(config_filename=None): # api blueprints application.register_blueprint(keyword_blueprint) application.register_blueprint(shares_blueprint) + application.register_blueprint(share_price_blueprint) application.register_blueprint(transaction_blueprint) application.register_blueprint(portfolio_blueprint) application.register_blueprint(users_blueprint) diff --git a/api/app/blueprints/share_price.py b/api/app/blueprints/share_price.py new file mode 100644 index 0000000..c313cda --- /dev/null +++ b/api/app/blueprints/share_price.py @@ -0,0 +1,88 @@ +import datetime +import os + +from apiflask import APIBlueprint, abort + +from app.models import SharePrice +from app.db import database as db +from app.helper_functions import make_response +from app.auth import auth +from app.schema import SymbolPriceSchema + +share_price_blueprint = APIBlueprint('share_price', __name__, url_prefix='/api') +__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) + + +@share_price_blueprint.route('/symbols', methods=['GET']) +@share_price_blueprint.output({}, 200) +@share_price_blueprint.auth_required(auth) +@share_price_blueprint.doc(summary="Returns all transaction symbols", description="Returns all transaction symbols for all users") +def get_transaction_symbols(): + symbols = db.session.execute("SELECT symbol FROM `transactions` GROUP BY symbol;").all() + + return_symbols = [] + for s in symbols: + return_symbols.append(s[0]) + + return make_response(return_symbols, 200, "Successfully loaded symbols") + + +@share_price_blueprint.route('/symbol', methods=['POST']) +@share_price_blueprint.output({}, 200) +@share_price_blueprint.input(schema=SymbolPriceSchema) +@share_price_blueprint.auth_required(auth) +@share_price_blueprint.doc(summary="Returns all transaction symbols", description="Returns all transaction symbols for all users") +def add_symbol_price(data): + if not check_if_symbol_data_exists(data): + abort(400, message="Symbol missing") + + if not check_if_price_data_exists(data): + abort(400, message="Price missing") + + if not check_if_time_data_exists(data): + abort(400, message="Time missing") + + symbol = data['symbol'] + price = data['price'] + time = data['time'] + + share_price = SharePrice( + symbol=symbol, + price=price, + date=datetime.datetime.strptime(time, '%Y-%m-%dT%H:%M:%S.%fZ'), + ) + + db.session.add(share_price) + db.session.commit() + + return make_response(share_price.as_dict(), 200, "Successfully added price") + + +def check_if_symbol_data_exists(data): + if 'symbol' not in data: + return False + + if data['symbol'] == "" or data['symbol'] is None: + return False + + return True + + +def check_if_price_data_exists(data): + if 'price' not in data: + return False + + if data['price'] == "" or data['price'] is None: + return False + + return True + + +def check_if_time_data_exists(data): + if 'time' not in data: + return False + + if data['time'] == "" or data['time'] is None: + return False + + return True diff --git a/api/app/blueprints/transactions.py b/api/app/blueprints/transactions.py index 33eae41..71c856a 100644 --- a/api/app/blueprints/transactions.py +++ b/api/app/blueprints/transactions.py @@ -1,13 +1,13 @@ -import os import datetime +import os from apiflask import abort, APIBlueprint +from app.auth import auth from app.db import database as db from app.helper_functions import make_response, get_email_or_abort_401 from app.models import Transaction from app.schema import TransactionSchema, TransactionResponseSchema -from app.auth import auth transaction_blueprint = APIBlueprint('transaction', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) diff --git a/api/app/models.py b/api/app/models.py index 14b42ac..7889271 100644 --- a/api/app/models.py +++ b/api/app/models.py @@ -51,3 +51,14 @@ class Share(db.Model): def as_dict(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} + + +class SharePrice(db.Model): + __tablename__ = 'share_price' + id = db.Column('id', db.Integer(), nullable=False, unique=True, primary_key=True) + symbol = db.Column('symbol', db.String(255)) + price = db.Column('price', db.Float()) + date = db.Column('date', db.DateTime()) + + def as_dict(self): + return {c.name: getattr(self, c.name) for c in self.__table__.columns} diff --git a/api/app/schema.py b/api/app/schema.py index 834ada3..7caad63 100644 --- a/api/app/schema.py +++ b/api/app/schema.py @@ -75,6 +75,12 @@ class TransactionSchema(Schema): price = Float() +class SymbolPriceSchema(Schema): + symbol = String() + time = String(validate=validate.Regexp(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z")) + price = Float() + + class TelegramIdSchema(Schema): telegram_user_id = String() diff --git a/api/generate_sample_transactions.py b/api/generate_sample_transactions.py index adfbec4..8ef4542 100644 --- a/api/generate_sample_transactions.py +++ b/api/generate_sample_transactions.py @@ -1,9 +1,9 @@ +import os import random import faker import requests -url = 'http://127.0.0.1:5000/api' username = '' password = '' @@ -12,7 +12,7 @@ shares = ["TWTR", "GOOG", "AAPL", "MSFT", "AMZN", "FB", "NFLX", "TSLA", "BABA", fake = faker.Faker() -token = requests.post(url + '/user/login', json={"email": username, "password": password}).json()['data']['token'] +token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": username, "password": password}).json()['data']['token'] for i in range(1, 1000): payload = { @@ -22,4 +22,4 @@ for i in range(1, 1000): "time": fake.date_time().isoformat() + ".000Z" } - response = requests.post(url + '/transaction', json=payload, headers={'Authorization': 'Bearer ' + token}) + response = requests.post(os.getenv("API_URL") + '/transaction', json=payload, headers={'Authorization': 'Bearer ' + token}) diff --git a/api/load_share_price.py b/api/load_share_price.py new file mode 100644 index 0000000..c58f7a9 --- /dev/null +++ b/api/load_share_price.py @@ -0,0 +1,33 @@ +import datetime +import os +import threading + +import requests +import yfinance + + +def thread_function(s): + my_share_info = yfinance.Ticker(s) + my_share_data = my_share_info.info + + if my_share_data['regularMarketPrice'] is not None: + payload = { + "symbol": s, + "price": float(my_share_data['regularMarketPrice']), + "time": datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.000Z") + } + + requests.post(os.getenv("API_URL") + '/symbol', json=payload, headers={'Authorization': 'Bearer ' + token}) + + +username = os.getenv('ADMIN_EMAIL') +password = os.getenv('ADMIN_PASSWORD') + +token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": username, "password": password}).json()['data']['token'] + +response = requests.get(os.getenv("API_URL") + '/symbols', headers={'Authorization': 'Bearer ' + token}).json()['data'] + +for symbol in response: + x = threading.Thread(target=thread_function, args=(symbol,)) + x.start() + diff --git a/api/requirements.txt b/api/requirements.txt index 6edbe0f..db4e4a4 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -11,4 +11,6 @@ bcrypt==3.2.0 pytest~=7.1.1 pytest-cov marshmallow~=3.15.0 -faker~=4.0.0 \ No newline at end of file +faker~=4.0.0 +yfinance~=0.1.6 +requests~=2.22.0 \ No newline at end of file From 5311c891939baa452bf89f2bc832e7da16ac83e6 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 5 Apr 2022 15:12:51 +0200 Subject: [PATCH 159/263] minor updates --- telegram_bot/bot_updates.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index e1fe445..e729e8a 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -40,7 +40,7 @@ def main_loop(): print(time.ctime()) # Debug - p1 = Process(target= update_crontab, args=(current_time_datetime, )) #Test threading instead of multiprocessing + p1 = Process(target= update_crontab, args=(current_time_datetime, )) p1.start() p3 = Process(target= update_based_on_crontab, args=(current_time_datetime, ) ) p3.daemon = True @@ -73,6 +73,7 @@ def update_crontab(pCurrent_Time): time_difference = datetime.datetime.now() - pCurrent_Time if float(str(time_difference).split(':')[0]) >=1: update_crontab(datetime.datetime.now()) + break def update_based_on_crontab(pCurrent_Time): From aee793deb4ed2775ef1cdf8a1e4fa8732e5f5024 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 5 Apr 2022 15:17:04 +0200 Subject: [PATCH 160/263] creating sends from crontab in work --- telegram_bot/bot_updates.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index e729e8a..5467aad 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -78,13 +78,29 @@ def update_crontab(pCurrent_Time): def update_based_on_crontab(pCurrent_Time): current_time_ctime = time.ctime() + + if str(current_time_ctime).split(' ')[0] == "Mon": + current_day = 0 + elif str(current_time_ctime).split(' ')[0] == "Tue": + current_day = 1 + elif str(current_time_ctime).split(' ')[0] == "Wed": + current_day = 3 + elif str(current_time_ctime).split(' ')[0] == "Thu": + current_day = 4 + elif str(current_time_ctime).split(' ')[0] == "Fri": + current_day = 5 + elif str(current_time_ctime).split(' ')[0] == "Sat": + current_day = 6 + elif str(current_time_ctime).split(' ')[0] == "Sun": + current_day = 7 + else: + print('Error with time code') + sys.exit(-1) + for i in range(len(user_crontab)): print('tbd') - time.sleep(60) - - def send_to_user(pText, pUser_id = 1770205310): From 081176cbc91bbb4bc85560149ac72ea2b3db52b4 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 5 Apr 2022 15:30:30 +0200 Subject: [PATCH 161/263] Return current price from database --- api/app/blueprints/portfolio.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/api/app/blueprints/portfolio.py b/api/app/blueprints/portfolio.py index 465ce47..2d2cde9 100644 --- a/api/app/blueprints/portfolio.py +++ b/api/app/blueprints/portfolio.py @@ -2,10 +2,11 @@ import os from apiflask import APIBlueprint -from app.schema import PortfolioResponseSchema +from app.models import SharePrice +from app.auth import auth from app.db import database as db from app.helper_functions import make_response, get_email_or_abort_401 -from app.auth import auth +from app.schema import PortfolioResponseSchema portfolio_blueprint = APIBlueprint('portfolio', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -23,11 +24,18 @@ def get_portfolio(): if transactions is not None: for row in transactions: - return_portfolio.append({ + data = { "symbol": row[0], "count": row[1], - # "price": row[2], - "last_transaction": row[3] - }) + # "calculated_price": row[2], + "last_transaction": row[3], + 'current_price': 0 + } + + query_share_price = db.session.query(SharePrice).filter_by(symbol=row[0]).first() + if query_share_price is not None: + data['current_price'] = query_share_price.as_dict()['price'] + + return_portfolio.append(data) return make_response(return_portfolio, 200, "Successfully loaded symbols") From 84754dd54f46b2b2d6ecc9f74e80cf89941ce984 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 5 Apr 2022 18:23:11 +0200 Subject: [PATCH 162/263] Update to bot_updates --- telegram_bot/bot_updates.py | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 5467aad..a709bf7 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -78,28 +78,49 @@ def update_crontab(pCurrent_Time): def update_based_on_crontab(pCurrent_Time): current_time_ctime = time.ctime() + ctime_split = str(current_time_ctime).split(' ') - if str(current_time_ctime).split(' ')[0] == "Mon": + if [0] == "Mon": current_day = 0 - elif str(current_time_ctime).split(' ')[0] == "Tue": + elif ctime_split[0] == "Tue": current_day = 1 - elif str(current_time_ctime).split(' ')[0] == "Wed": + elif ctime_split[0] == "Wed": current_day = 3 - elif str(current_time_ctime).split(' ')[0] == "Thu": + elif ctime_split[0] == "Thu": current_day = 4 - elif str(current_time_ctime).split(' ')[0] == "Fri": + elif ctime_split[0] == "Fri": current_day = 5 - elif str(current_time_ctime).split(' ')[0] == "Sat": + elif ctime_split[0] == "Sat": current_day = 6 - elif str(current_time_ctime).split(' ')[0] == "Sun": + elif ctime_split[0] == "Sun": current_day = 7 else: print('Error with time code') sys.exit(-1) for i in range(len(user_crontab)): - print('tbd') + day_match = False + hour_match = False + user_crontab[i] = user_crontab.strip() + + contrab_split = str(user_crontab[i]).split(' ') + + if ',' in contrab_split[4]: # if the user wants to be alerted on multiple specific days + contrab_days = contrab_split[4].split(',') + else: + contrab_days = contrab_days + + for element in contrab_days: + if str(current_day) == element or element=='*': + hour_match = True + + + + + if hour_match and day_match: + send_to_user("regular update", pUser_id=user_crontab[i]) + def send_to_user(pText, pUser_id = 1770205310): From 41dd16e81fa247728757e11b166c8d8d8f9bd29e Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 5 Apr 2022 19:08:09 +0200 Subject: [PATCH 163/263] Updated Dockerfile --- api/Dockerfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api/Dockerfile b/api/Dockerfile index cfc57aa..c9a163b 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,16 +1,16 @@ -FROM python:3.10-alpine +FROM python:3.10-slim -# Change the working directory to the project root +# Change the working directory to the root of the project WORKDIR /srv/flask_app # Install dependencies -RUN apk add nginx build-base libffi-dev curl uwsgi +RUN apt update && apt install -y python3 python3-pip curl nginx && rm -rf /var/lib/apt/lists/* -# Install python dependencies +# Install the dependencies COPY api/requirements.txt /srv/flask_app/ RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-location -# Copy the app +# Copy the source code to the working directory COPY api /srv/flask_app COPY api/deploy/nginx.conf /etc/nginx From f64a6e0a07015149e401d519c51babc95ca29e3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Apr 2022 09:34:26 +0000 Subject: [PATCH 164/263] Update requests requirement from ~=2.22.0 to ~=2.27.1 in /api Updates the requirements on [requests](https://github.com/psf/requests) to permit the latest version. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.22.0...v2.27.1) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- api/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/requirements.txt b/api/requirements.txt index db4e4a4..9378077 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -13,4 +13,4 @@ pytest-cov marshmallow~=3.15.0 faker~=4.0.0 yfinance~=0.1.6 -requests~=2.22.0 \ No newline at end of file +requests~=2.27.1 \ No newline at end of file From 90494dece3f99152c3d43e5d10a021b53dff1657 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Apr 2022 09:34:34 +0000 Subject: [PATCH 165/263] Update faker requirement from ~=4.0.0 to ~=13.3.4 in /api Updates the requirements on [faker](https://github.com/joke2k/faker) to permit the latest version. - [Release notes](https://github.com/joke2k/faker/releases) - [Changelog](https://github.com/joke2k/faker/blob/master/CHANGELOG.md) - [Commits](https://github.com/joke2k/faker/compare/v4.0.0...v13.3.4) --- updated-dependencies: - dependency-name: faker dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- api/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/requirements.txt b/api/requirements.txt index db4e4a4..6571163 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -11,6 +11,6 @@ bcrypt==3.2.0 pytest~=7.1.1 pytest-cov marshmallow~=3.15.0 -faker~=4.0.0 +faker~=13.3.4 yfinance~=0.1.6 requests~=2.22.0 \ No newline at end of file From cb7cc145298880355557e02201c09e8a9bcaa6c6 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Wed, 6 Apr 2022 12:34:03 +0200 Subject: [PATCH 166/263] Prevent "out of memory" issue --- api/load_share_price.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/api/load_share_price.py b/api/load_share_price.py index c58f7a9..e0e9ea7 100644 --- a/api/load_share_price.py +++ b/api/load_share_price.py @@ -1,9 +1,11 @@ import datetime import os import threading +import time import requests import yfinance +from dotenv import load_dotenv def thread_function(s): @@ -20,6 +22,12 @@ def thread_function(s): requests.post(os.getenv("API_URL") + '/symbol', json=payload, headers={'Authorization': 'Bearer ' + token}) +def split(a, n): + k, m = divmod(len(a), n) + return (a[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(n)) + + +load_dotenv() username = os.getenv('ADMIN_EMAIL') password = os.getenv('ADMIN_PASSWORD') @@ -27,7 +35,10 @@ token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": usern response = requests.get(os.getenv("API_URL") + '/symbols', headers={'Authorization': 'Bearer ' + token}).json()['data'] -for symbol in response: - x = threading.Thread(target=thread_function, args=(symbol,)) - x.start() +symbols = split(response, int(len(response) / 5)) +for symbol_list in symbols: + for symbol in symbol_list: + x = threading.Thread(target=thread_function, args=(symbol,)) + x.start() + time.sleep(10) From d3cf0e0c374aaa42bd1923b2e4b525125e1199a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Apr 2022 10:35:50 +0000 Subject: [PATCH 167/263] Update yfinance requirement from ~=0.1.6 to ~=0.1.70 in /api Updates the requirements on [yfinance](https://github.com/ranaroussi/yfinance) to permit the latest version. - [Release notes](https://github.com/ranaroussi/yfinance/releases) - [Changelog](https://github.com/ranaroussi/yfinance/blob/main/CHANGELOG.rst) - [Commits](https://github.com/ranaroussi/yfinance/compare/0.1.55...0.1.70) --- updated-dependencies: - dependency-name: yfinance dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- api/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/requirements.txt b/api/requirements.txt index f6a8369..0824c0d 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -12,5 +12,5 @@ pytest~=7.1.1 pytest-cov marshmallow~=3.15.0 faker~=13.3.4 -yfinance~=0.1.6 +yfinance~=0.1.70 requests~=2.27.1 \ No newline at end of file From 813231449e6b0101a742bd7913bcd828e589082a Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Wed, 6 Apr 2022 14:26:52 +0200 Subject: [PATCH 168/263] Mistake corrected --- telegram_bot/bot_updates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index a709bf7..821de90 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -80,7 +80,7 @@ def update_based_on_crontab(pCurrent_Time): current_time_ctime = time.ctime() ctime_split = str(current_time_ctime).split(' ') - if [0] == "Mon": + if ctime_split[0] == "Mon": current_day = 0 elif ctime_split[0] == "Tue": current_day = 1 From 85cd363c06df2fa4b6db066e2f6eec247d3d8c71 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Thu, 7 Apr 2022 12:40:55 +0200 Subject: [PATCH 169/263] Always use the most recent share price --- api/app/blueprints/portfolio.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/app/blueprints/portfolio.py b/api/app/blueprints/portfolio.py index 2d2cde9..e5eb5fb 100644 --- a/api/app/blueprints/portfolio.py +++ b/api/app/blueprints/portfolio.py @@ -1,11 +1,10 @@ import os from apiflask import APIBlueprint - -from app.models import SharePrice from app.auth import auth from app.db import database as db from app.helper_functions import make_response, get_email_or_abort_401 +from app.models import SharePrice from app.schema import PortfolioResponseSchema portfolio_blueprint = APIBlueprint('portfolio', __name__, url_prefix='/api') @@ -32,7 +31,7 @@ def get_portfolio(): 'current_price': 0 } - query_share_price = db.session.query(SharePrice).filter_by(symbol=row[0]).first() + query_share_price = db.session.query(SharePrice).filter_by(symbol=row[0]).order_by(SharePrice.date.desc()).first() if query_share_price is not None: data['current_price'] = query_share_price.as_dict()['price'] From 739c10fc45d2212f6f40b36beed3c8272fca7c1d Mon Sep 17 00:00:00 2001 From: Florian Kaiser <44125287+H4CK3R-01@users.noreply.github.com> Date: Tue, 12 Apr 2022 09:45:39 +0200 Subject: [PATCH 170/263] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 8e1916a..1bc17c9 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,12 @@ WebEngineering2 Projekt: Aktien und News Bot für Telegram ## Dokumentation -> README.md in /documentation +## Team +* Florian Kaiser +* Florian Kellermann +* Linus Eickhof +* Kevin Pauer + ## Nützliche Tools - Portainer (https://gruppe1.testsites.info/portainer/) \ *Container Management System* From 4e072323d25b56b90157cfb0d3ed76c34c1a5fd6 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 12 Apr 2022 09:50:24 +0200 Subject: [PATCH 171/263] Added file headers --- api/app.py | 6 ++++++ api/app/__init__.py | 6 ++++++ api/app/auth.py | 6 ++++++ api/app/blueprints/__init__.py | 5 +++++ api/app/blueprints/keyword.py | 6 ++++++ api/app/blueprints/portfolio.py | 6 ++++++ api/app/blueprints/share_price.py | 6 ++++++ api/app/blueprints/shares.py | 6 ++++++ api/app/blueprints/telegram.py | 6 ++++++ api/app/blueprints/transactions.py | 6 ++++++ api/app/blueprints/user.py | 6 ++++++ api/app/config/flask.cfg | 6 ++++++ api/app/config/flask_test.cfg | 6 ++++++ api/app/db.py | 6 ++++++ api/app/helper_functions.py | 6 ++++++ api/app/models.py | 6 ++++++ api/app/schema.py | 6 ++++++ api/generate_sample_transactions.py | 6 ++++++ api/load_share_price.py | 6 ++++++ api/tests/conftest.py | 6 ++++++ api/tests/functional/__init__.py | 5 +++++ api/tests/functional/helper_functions.py | 6 ++++++ api/tests/functional/test_keyword.py | 6 ++++++ api/tests/functional/test_portfolio.py | 6 ++++++ api/tests/functional/test_share.py | 6 ++++++ api/tests/functional/test_telegram.py | 6 ++++++ api/tests/functional/test_transaction.py | 6 ++++++ api/tests/functional/test_user.py | 6 ++++++ api/tests/unit/__init__.py | 5 +++++ api/tests/unit/test_auth.py | 6 ++++++ api/tests/unit/test_helper_functions.py | 6 ++++++ api/tests/unit/test_models.py | 6 ++++++ api/tests/unit/test_transaction.py | 6 ++++++ api/tests/unit/test_user.py | 6 ++++++ 34 files changed, 201 insertions(+) diff --git a/api/app.py b/api/app.py index 3314930..28fe3e9 100644 --- a/api/app.py +++ b/api/app.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + from app import create_app # Create an application instance that web servers can use. diff --git a/api/app/__init__.py b/api/app/__init__.py index 8040125..30822d3 100644 --- a/api/app/__init__.py +++ b/api/app/__init__.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + from flask import current_app from apiflask import APIFlask diff --git a/api/app/auth.py b/api/app/auth.py index 513a889..e96d28a 100644 --- a/api/app/auth.py +++ b/api/app/auth.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + from flask import current_app import jwt diff --git a/api/app/blueprints/__init__.py b/api/app/blueprints/__init__.py index e69de29..dfaac6b 100644 --- a/api/app/blueprints/__init__.py +++ b/api/app/blueprints/__init__.py @@ -0,0 +1,5 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" diff --git a/api/app/blueprints/keyword.py b/api/app/blueprints/keyword.py index cd00c53..4412621 100644 --- a/api/app/blueprints/keyword.py +++ b/api/app/blueprints/keyword.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import os from apiflask import APIBlueprint, abort diff --git a/api/app/blueprints/portfolio.py b/api/app/blueprints/portfolio.py index e5eb5fb..2b6ab02 100644 --- a/api/app/blueprints/portfolio.py +++ b/api/app/blueprints/portfolio.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import os from apiflask import APIBlueprint diff --git a/api/app/blueprints/share_price.py b/api/app/blueprints/share_price.py index c313cda..aa2a302 100644 --- a/api/app/blueprints/share_price.py +++ b/api/app/blueprints/share_price.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import datetime import os diff --git a/api/app/blueprints/shares.py b/api/app/blueprints/shares.py index 24656a1..56dad69 100644 --- a/api/app/blueprints/shares.py +++ b/api/app/blueprints/shares.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import os from apiflask import APIBlueprint, abort diff --git a/api/app/blueprints/telegram.py b/api/app/blueprints/telegram.py index c96e923..10aad9f 100644 --- a/api/app/blueprints/telegram.py +++ b/api/app/blueprints/telegram.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import os from apiflask import APIBlueprint, abort diff --git a/api/app/blueprints/transactions.py b/api/app/blueprints/transactions.py index 71c856a..da3b7bf 100644 --- a/api/app/blueprints/transactions.py +++ b/api/app/blueprints/transactions.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import datetime import os diff --git a/api/app/blueprints/user.py b/api/app/blueprints/user.py index a570883..196af26 100644 --- a/api/app/blueprints/user.py +++ b/api/app/blueprints/user.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import datetime import os diff --git a/api/app/config/flask.cfg b/api/app/config/flask.cfg index 25babd4..2fec222 100644 --- a/api/app/config/flask.cfg +++ b/api/app/config/flask.cfg @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import os from app.schema import BaseResponseSchema diff --git a/api/app/config/flask_test.cfg b/api/app/config/flask_test.cfg index 2cc085b..fc8ac7f 100644 --- a/api/app/config/flask_test.cfg +++ b/api/app/config/flask_test.cfg @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import os from app.schema import BaseResponseSchema diff --git a/api/app/db.py b/api/app/db.py index d09ee30..22b1d85 100644 --- a/api/app/db.py +++ b/api/app/db.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + from flask_sqlalchemy import SQLAlchemy # database object diff --git a/api/app/helper_functions.py b/api/app/helper_functions.py index ee90298..d3edea3 100644 --- a/api/app/helper_functions.py +++ b/api/app/helper_functions.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import bcrypt import jwt from apiflask import abort diff --git a/api/app/models.py b/api/app/models.py index 7889271..864ce32 100644 --- a/api/app/models.py +++ b/api/app/models.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + from app.db import database as db diff --git a/api/app/schema.py b/api/app/schema.py index 7caad63..f48f232 100644 --- a/api/app/schema.py +++ b/api/app/schema.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + from apiflask import Schema from apiflask.fields import Integer, String, Boolean, Field, Float from marshmallow import validate diff --git a/api/generate_sample_transactions.py b/api/generate_sample_transactions.py index 8ef4542..3c28db4 100644 --- a/api/generate_sample_transactions.py +++ b/api/generate_sample_transactions.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import os import random diff --git a/api/load_share_price.py b/api/load_share_price.py index e0e9ea7..bd77ee4 100644 --- a/api/load_share_price.py +++ b/api/load_share_price.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import datetime import os import threading diff --git a/api/tests/conftest.py b/api/tests/conftest.py index e1f32fc..be4933c 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import pytest from app import create_app, db from app.models import User, Transaction, Keyword, Share diff --git a/api/tests/functional/__init__.py b/api/tests/functional/__init__.py index e69de29..dfaac6b 100644 --- a/api/tests/functional/__init__.py +++ b/api/tests/functional/__init__.py @@ -0,0 +1,5 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" diff --git a/api/tests/functional/helper_functions.py b/api/tests/functional/helper_functions.py index 2b88442..8c93f71 100644 --- a/api/tests/functional/helper_functions.py +++ b/api/tests/functional/helper_functions.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + import json diff --git a/api/tests/functional/test_keyword.py b/api/tests/functional/test_keyword.py index 3517c18..3ce610d 100644 --- a/api/tests/functional/test_keyword.py +++ b/api/tests/functional/test_keyword.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + """ This file (test_keyword.py) contains the functional tests for the `keyword` blueprint. """ diff --git a/api/tests/functional/test_portfolio.py b/api/tests/functional/test_portfolio.py index f240c93..b2278ba 100644 --- a/api/tests/functional/test_portfolio.py +++ b/api/tests/functional/test_portfolio.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + """ This file (test_portfolio.py) contains the functional tests for the `portfolio` blueprint. """ diff --git a/api/tests/functional/test_share.py b/api/tests/functional/test_share.py index 8df2ced..875bc0b 100644 --- a/api/tests/functional/test_share.py +++ b/api/tests/functional/test_share.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + """ This file (test_share.py) contains the functional tests for the `share` blueprint. """ diff --git a/api/tests/functional/test_telegram.py b/api/tests/functional/test_telegram.py index 34acee7..17679df 100644 --- a/api/tests/functional/test_telegram.py +++ b/api/tests/functional/test_telegram.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + """ This file (test_telegram.py) contains the functional tests for the `telegram` blueprint. """ diff --git a/api/tests/functional/test_transaction.py b/api/tests/functional/test_transaction.py index cb7b9ad..2f1917d 100644 --- a/api/tests/functional/test_transaction.py +++ b/api/tests/functional/test_transaction.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + """ This file (test_transaction.py) contains the functional tests for the `transaction` blueprint. """ diff --git a/api/tests/functional/test_user.py b/api/tests/functional/test_user.py index 80fdaec..8b4c1eb 100644 --- a/api/tests/functional/test_user.py +++ b/api/tests/functional/test_user.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + """ This file (test_user.py) contains the functional tests for the `users` blueprint. """ diff --git a/api/tests/unit/__init__.py b/api/tests/unit/__init__.py index e69de29..dfaac6b 100644 --- a/api/tests/unit/__init__.py +++ b/api/tests/unit/__init__.py @@ -0,0 +1,5 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" diff --git a/api/tests/unit/test_auth.py b/api/tests/unit/test_auth.py index cf35577..110e7ae 100644 --- a/api/tests/unit/test_auth.py +++ b/api/tests/unit/test_auth.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + """ This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. """ diff --git a/api/tests/unit/test_helper_functions.py b/api/tests/unit/test_helper_functions.py index ef84fc4..0c61bcf 100644 --- a/api/tests/unit/test_helper_functions.py +++ b/api/tests/unit/test_helper_functions.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + """ This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. """ diff --git a/api/tests/unit/test_models.py b/api/tests/unit/test_models.py index 690b58a..57618a9 100644 --- a/api/tests/unit/test_models.py +++ b/api/tests/unit/test_models.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + """ This file (test_models.py) contains the unit tests for the models.py file. """ diff --git a/api/tests/unit/test_transaction.py b/api/tests/unit/test_transaction.py index 66f51ae..91f8e25 100644 --- a/api/tests/unit/test_transaction.py +++ b/api/tests/unit/test_transaction.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + """ This file (test_transaction.py) contains the unit tests for the blueprints/transaction.py file. """ diff --git a/api/tests/unit/test_user.py b/api/tests/unit/test_user.py index eb5706d..2846fe7 100644 --- a/api/tests/unit/test_user.py +++ b/api/tests/unit/test_user.py @@ -1,3 +1,9 @@ +__author__ = "Florian Kaiser" +__copyright__ = "Copyright 2022, Project Aktienbot" +__credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] +__license__ = "GPL 3.0" +__version__ = "1.0.0" + """ This file (test_helper_functions.py) contains the unit tests for the helper_functions.py file. """ From 6cea0f041bf7baa4f9f5c02361a09e96027bbc99 Mon Sep 17 00:00:00 2001 From: Rripped <75929322+Rripped@users.noreply.github.com> Date: Tue, 12 Apr 2022 09:51:59 +0200 Subject: [PATCH 172/263] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1bc17c9..0bb8abd 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ WebEngineering2 Projekt: Aktien und News Bot für Telegram ## Team * Florian Kaiser * Florian Kellermann -* Linus Eickhof +* Linus Eickhoff * Kevin Pauer ## Nützliche Tools From 27858a3b5993eacd16da6d5004d8c6f23394259f Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 12 Apr 2022 10:08:41 +0200 Subject: [PATCH 173/263] added crontab put function --- telegram_bot/api_handling/api_handler.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index 88810ce..bc8bc8a 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -263,7 +263,20 @@ class API_Handler: else: return self.get_user_portfolio(user_id, max_retries-1) + def set_cron_interval(self, user_id, cron_interval): + """sets the cron interval of the user + Args: + user_id (int): id of the user + cron_interval (String): Update interval in cron format => see https://crontab.guru/ for formatting + + Returns: + int: status code + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.put(self.db_adress + "/user/setCron", json={"cron": cron_interval}, headers=headers) + return req.status_code if __name__ == "__main__": From 2e48d2a27ee0a3b5621f471f2a81d07ad92f11d1 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 12 Apr 2022 10:12:35 +0200 Subject: [PATCH 174/263] removed unused --- telegram_bot/news/news_fetcher.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/telegram_bot/news/news_fetcher.py b/telegram_bot/news/news_fetcher.py index 332dafd..2b79721 100644 --- a/telegram_bot/news/news_fetcher.py +++ b/telegram_bot/news/news_fetcher.py @@ -6,7 +6,6 @@ __date__ = "15.03.2022" __version__ = "0.0.1" __license__ = "None" -from ast import parse import sys import os import json @@ -26,7 +25,7 @@ sources = source_json["sources"] str_sources = ",".join([source["id"] for source in sources]) -def get_all_news_by_keyword(keyword, from_date="2000-01-01"): # hard coded will change soon +def get_all_news_by_keyword(keyword, from_date="2000-01-01"): """get all news to keyword Args: keyword (String): keyword for search @@ -80,4 +79,5 @@ if __name__ == '__main__': articles = get_all_news_by_keyword("bitcoin") formatted_article = format_article(articles["articles"][0]) - print(formatted_article) \ No newline at end of file + print(formatted_article) + sys.exit(1) \ No newline at end of file From 30bccbf2381d884096f348626a6a16763c0cd169 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 12 Apr 2022 10:41:48 +0200 Subject: [PATCH 175/263] added user funcs of api --- telegram_bot/api_handling/api_handler.py | 48 +++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index bc8bc8a..33c9a41 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -74,6 +74,51 @@ class API_Handler: return None + def get_user(self, user_id, max_retries=10): + """gets the shares of the user + + Args: + user_id (int): id of the user + max_retries (int): max retries for the request + + Returns: + json: json of user infos + """ + if max_retries <= 0: + return None + + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + req = s.get(self.db_adress + "/user", headers=headers) + if(req.status_code == 200): + return req.json()["data"] + + else: + return self.get_user(user_id, max_retries-1) + + + def get_all_users(self, max_retries=10): + """gets all users + + Args: + max_retries (int): max retries for the request + + Returns: + list: list of users + """ + if max_retries <= 0: + return None + + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token} + req = s.get(self.db_adress + "/users", headers=headers) + if(req.status_code == 200): + return req.json()["data"] + + else: + return self.get_all_users(max_retries-1) + + def get_user_keywords(self, user_id, max_retries=10): """gets the keywords of the user @@ -282,10 +327,11 @@ class API_Handler: if __name__ == "__main__": print("This is a module for the telegram bot. It is not intended to be run directly.") - handler = API_Handler("https://aktienbot.flokaiser.com/api", "bot@example.com", "bot") + handler = API_Handler("https://gruppe1.testsites.info/api", "bot@example.com", "bot") print(handler.token) keywords = handler.get_user_keywords(user_id = 1709356058) #user_id is currently mine (Linus) print(keywords) shares = handler.get_user_portfolio(user_id = 1709356058) + print(handler.set_cron_interval(user_id = 1709356058, cron_interval = "0 0 * * *")) print(shares) sys.exit(1) \ No newline at end of file From 561ce58d73274d147569f54613940cf054f18c0b Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 12 Apr 2022 11:32:33 +0200 Subject: [PATCH 176/263] added new lib --- telegram_bot/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/telegram_bot/requirements.txt b/telegram_bot/requirements.txt index 6b0de09..2e8462d 100644 --- a/telegram_bot/requirements.txt +++ b/telegram_bot/requirements.txt @@ -4,3 +4,4 @@ yfinance~=0.1.70 newsapi-python~=0.2.6 python-dotenv~=0.20.0 requests~=2.27.1 +APScheduler~=3.9.1 From c8d461b747ea6746d0dfc6065a831c003f4083c7 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 12 Apr 2022 11:33:21 +0200 Subject: [PATCH 177/263] tested new methods --- telegram_bot/api_handling/api_handler.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index 33c9a41..b1ce853 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -332,6 +332,10 @@ if __name__ == "__main__": keywords = handler.get_user_keywords(user_id = 1709356058) #user_id is currently mine (Linus) print(keywords) shares = handler.get_user_portfolio(user_id = 1709356058) - print(handler.set_cron_interval(user_id = 1709356058, cron_interval = "0 0 * * *")) + print("set cron with status: "+ str(handler.set_cron_interval(user_id = 1709356058, cron_interval = "0 0 * * *"))) + user = handler.get_user(user_id = 1709356058) + print(user) + all_users = handler.get_all_users() + print(all_users) print(shares) sys.exit(1) \ No newline at end of file From 1095c6378850de3dbb831f51eb5a43809c0f4697 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 12 Apr 2022 11:33:53 +0200 Subject: [PATCH 178/263] added scheduler --- telegram_bot/bot_scheduler.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 telegram_bot/bot_scheduler.py diff --git a/telegram_bot/bot_scheduler.py b/telegram_bot/bot_scheduler.py new file mode 100644 index 0000000..e69de29 From 24c2702f1027ac634b55a7369ee1ae03ab059590 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 12 Apr 2022 11:36:23 +0200 Subject: [PATCH 179/263] Added comments --- api/app/auth.py | 4 +- api/app/blueprints/keyword.py | 15 ++++--- api/app/blueprints/portfolio.py | 5 +++ api/app/blueprints/share_price.py | 13 +++--- api/app/blueprints/shares.py | 22 +++++----- api/app/blueprints/telegram.py | 2 + api/app/blueprints/transactions.py | 5 +++ api/app/blueprints/user.py | 65 +++++++++++++++++------------ api/app/helper_functions.py | 49 ++++++++++++++++++++-- api/app/schema.py | 1 + api/generate_sample_transactions.py | 8 ++-- 11 files changed, 132 insertions(+), 57 deletions(-) diff --git a/api/app/auth.py b/api/app/auth.py index e96d28a..e1a4bba 100644 --- a/api/app/auth.py +++ b/api/app/auth.py @@ -17,7 +17,9 @@ def verify_token(token): if token is None: return False - if ':' in token: # Bot token + # We decided to append the user id to the bearer token using ":" as separator to select an specific user + # To validate the token we can remove the user id since we only validate the token and not the user id + if ':' in token: token = token.split(":")[0] try: diff --git a/api/app/blueprints/keyword.py b/api/app/blueprints/keyword.py index 4412621..e3edc21 100644 --- a/api/app/blueprints/keyword.py +++ b/api/app/blueprints/keyword.py @@ -31,9 +31,10 @@ def add_keyword(data): key = data['keyword'] + # Check if keyword already exists check_keyword = db.session.query(Keyword).filter_by(keyword=key, email=email).first() if check_keyword is None: - # Keyword doesn't exist yet for this user + # Keyword doesn't exist yet for this user -> add it new_keyword = Keyword( email=email, keyword=key @@ -54,17 +55,17 @@ def add_keyword(data): def remove_keyword(data): email = get_email_or_abort_401() + # Check if request data is valid if not check_if_keyword_data_exists(data): abort(400, message="Keyword missing") - key = data['keyword'] - - check_keyword = db.session.query(Keyword).filter_by(keyword=key, email=email).first() - + # Check if keyword exists + check_keyword = db.session.query(Keyword).filter_by(keyword=data['keyword'], email=email).first() if check_keyword is None: return abort(500, "Keyword doesn't exist for this user") else: - db.session.query(Keyword).filter_by(keyword=key, email=email).delete() + # Keyword exists -> delete it + db.session.query(Keyword).filter_by(keyword=data['keyword'], email=email).delete() db.session.commit() return make_response({}, 200, "Successfully removed keyword") @@ -80,6 +81,8 @@ def get_keywords(): return_keywords = [] keywords = db.session.query(Keyword).filter_by(email=email).all() + # If no keywords exist for this user -> return empty list + # Otherwise iterate over all keywords, convert them to json and add them to the return list if keywords is not None: for row in keywords: return_keywords.append(row.as_dict()) diff --git a/api/app/blueprints/portfolio.py b/api/app/blueprints/portfolio.py index 2b6ab02..2cefe4d 100644 --- a/api/app/blueprints/portfolio.py +++ b/api/app/blueprints/portfolio.py @@ -25,8 +25,12 @@ def get_portfolio(): email = get_email_or_abort_401() return_portfolio = [] + + # Get all transactions of current user transactions = db.session.execute("SELECT symbol, SUM(count), SUM(price), MAX(time) FROM `transactions` WHERE email = '" + email + "' GROUP BY symbol;").all() + # If there are no transactions, return empty portfolio + # Otherwise calculate portfolio if transactions is not None: for row in transactions: data = { @@ -37,6 +41,7 @@ def get_portfolio(): 'current_price': 0 } + # Add current share value to portfolio query_share_price = db.session.query(SharePrice).filter_by(symbol=row[0]).order_by(SharePrice.date.desc()).first() if query_share_price is not None: data['current_price'] = query_share_price.as_dict()['price'] diff --git a/api/app/blueprints/share_price.py b/api/app/blueprints/share_price.py index aa2a302..d63a844 100644 --- a/api/app/blueprints/share_price.py +++ b/api/app/blueprints/share_price.py @@ -24,6 +24,7 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file @share_price_blueprint.auth_required(auth) @share_price_blueprint.doc(summary="Returns all transaction symbols", description="Returns all transaction symbols for all users") def get_transaction_symbols(): + # Get all transaction symbols symbols = db.session.execute("SELECT symbol FROM `transactions` GROUP BY symbol;").all() return_symbols = [] @@ -39,6 +40,7 @@ def get_transaction_symbols(): @share_price_blueprint.auth_required(auth) @share_price_blueprint.doc(summary="Returns all transaction symbols", description="Returns all transaction symbols for all users") def add_symbol_price(data): + # Check if required data is available if not check_if_symbol_data_exists(data): abort(400, message="Symbol missing") @@ -48,14 +50,11 @@ def add_symbol_price(data): if not check_if_time_data_exists(data): abort(400, message="Time missing") - symbol = data['symbol'] - price = data['price'] - time = data['time'] - + # Add share price share_price = SharePrice( - symbol=symbol, - price=price, - date=datetime.datetime.strptime(time, '%Y-%m-%dT%H:%M:%S.%fZ'), + symbol=data['symbol'], + price=data['price'], + date=datetime.datetime.strptime(data['time'], '%Y-%m-%dT%H:%M:%S.%fZ'), ) db.session.add(share_price) diff --git a/api/app/blueprints/shares.py b/api/app/blueprints/shares.py index 56dad69..e015585 100644 --- a/api/app/blueprints/shares.py +++ b/api/app/blueprints/shares.py @@ -26,17 +26,17 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file def add_symbol(data): email = get_email_or_abort_401() + # Check if required data is available if not check_if_symbol_data_exists(data): abort(400, message="Symbol missing") - symbol = data['symbol'] - - check_share = db.session.query(Share).filter_by(symbol=symbol, email=email).first() + # Check if share already exists + check_share = db.session.query(Share).filter_by(symbol=data['symbol'], email=email).first() if check_share is None: - # Keyword doesn't exist yet for this user + # Keyword doesn't exist yet for this user -> add it new_symbol = Share( email=email, - symbol=symbol + symbol=data['symbol'] ) db.session.add(new_symbol) db.session.commit() @@ -54,17 +54,17 @@ def add_symbol(data): def remove_symbol(data): email = get_email_or_abort_401() + # Check if required data is available if not check_if_symbol_data_exists(data): abort(400, message="Symbol missing") - symbol = data['symbol'] - - check_share = db.session.query(Share).filter_by(symbol=symbol, email=email).first() - + # Check if share exists + check_share = db.session.query(Share).filter_by(symbol=data['symbol'], email=email).first() if check_share is None: abort(500, "Symbol doesn't exist for this user") else: - db.session.query(Share).filter_by(symbol=symbol, email=email).delete() + # Delete share + db.session.query(Share).filter_by(symbol=data['symbol'], email=email).delete() db.session.commit() return make_response({}, 200, "Successfully removed symbol") @@ -80,6 +80,8 @@ def get_symbol(): return_symbols = [] symbols = db.session.query(Share).filter_by(email=email).all() + # If no shares exist for this user -> return empty list + # Otherwise iterate over all shares, convert them to json and add them to the return list if symbols is not None: for row in symbols: return_symbols.append(row.as_dict()) diff --git a/api/app/blueprints/telegram.py b/api/app/blueprints/telegram.py index 10aad9f..b492a91 100644 --- a/api/app/blueprints/telegram.py +++ b/api/app/blueprints/telegram.py @@ -24,11 +24,13 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file def add_keyword(data): email = get_email_or_abort_401() + # Check if request data is valid if not check_if_telegram_user_id_data_exists(data): abort(400, message="User ID missing") query_user = get_user(email) + # Change user id query_user.telegram_user_id = data['telegram_user_id'] db.session.commit() diff --git a/api/app/blueprints/transactions.py b/api/app/blueprints/transactions.py index da3b7bf..2bc469b 100644 --- a/api/app/blueprints/transactions.py +++ b/api/app/blueprints/transactions.py @@ -27,6 +27,7 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file def add_transaction(data): email = get_email_or_abort_401() + # Check if required data is available if not check_if_symbol_data_exists(data): abort(400, "Symbol missing") @@ -39,6 +40,7 @@ def add_transaction(data): if not check_if_price_data_exists(data): abort(400, "Price missing") + # Add transaction new_transaction = Transaction( email=email, symbol=data['symbol'], @@ -60,8 +62,11 @@ def get_transaction(): email = get_email_or_abort_401() return_transactions = [] + + # Get all transactions transactions = db.session.query(Transaction).filter_by(email=email).all() + # Iterate over transactions and add them to return_transactions if transactions is not None: for row in transactions: return_transactions.append(row.as_dict()) diff --git a/api/app/blueprints/user.py b/api/app/blueprints/user.py index 196af26..cc6b510 100644 --- a/api/app/blueprints/user.py +++ b/api/app/blueprints/user.py @@ -28,6 +28,8 @@ def users(): abort_if_no_admin() res = [] + + # Query all users and convert them to dicts for i in User.query.all(): res.append(i.as_dict()) @@ -41,6 +43,7 @@ def users(): def user(): email = get_email_or_abort_401() + # Query current user query_user = get_user(email) return make_response(query_user.as_dict(), 200, "Successfully received current user data") @@ -51,23 +54,26 @@ def user(): @users_blueprint.input(schema=LoginDataSchema) @users_blueprint.doc(summary="Login", description="Returns jwt token if username and password match, otherwise returns error") def login(data): + # Check if required data is available if not check_if_password_data_exists(data): abort(400, "Password missing") if not check_if_email_data_exists(data): abort(400, "Email missing") - email = data['email'] - password = data['password'] + # Query current user + query_user = get_user(data['email']) - query_user = get_user(email) - - if not check_password(query_user.password, password.encode("utf-8")): # Password incorrect + # Check if password matches + if not check_password(query_user.password, data['password'].encode("utf-8")): # Password incorrect abort(500, message="Unable to login") + # Check if user is bot if query_user.email == current_app.config['BOT_EMAIL']: + # Set bot token valid for 1 year token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=365)}, current_app.config['SECRET_KEY'], "HS256") else: + # Set token valid for 1 day token = jwt.encode({'email': query_user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1)}, current_app.config['SECRET_KEY'], "HS256") return make_response({"token": token}, 200, "Successfully logged in") @@ -78,6 +84,7 @@ def login(data): @users_blueprint.input(schema=RegisterDataSchema) @users_blueprint.doc(summary="Register", description="Registers user") def register(data): + # Check if required data is available if not check_if_email_data_exists(data): abort(400, "Email missing") @@ -87,19 +94,16 @@ def register(data): if not check_if_password_data_exists(data): abort(400, "Password missing") - email = data['email'] - username = data['username'] - password = data['password'] - - query_user = db.session.query(User).filter_by(email=email).first() - - if query_user is not None: # Username already exist + # Check if user already exists + query_user = db.session.query(User).filter_by(email=data['email']).first() + if query_user is not None: abort(500, message="Email already exist") + # Add user to database new_user = User( - email=email, - username=username, - password=hash_password(password), + email=data['email'], + username=data['username'], + password=hash_password(data['password']), admin=False, cron="0 8 * * *" ) @@ -117,11 +121,14 @@ def register(data): def update_user(data): email = get_email_or_abort_401() + # Query current user query_user = get_user(email) + # Check if password data is available -> if, change password if check_if_password_data_exists(data): query_user.password = hash_password(data['password']) + # Check if username data is available -> if, change username if check_if_username_data_exists(data): query_user.username = data['username'] @@ -138,18 +145,18 @@ def update_user(data): def set_admin(data): abort_if_no_admin() # Only admin users can do this + # Check if required data is available if not check_if_email_data_exists(data): abort(400, "Email missing") if not check_if_admin_data_exists(data): abort(400, "Admin data missing") - email = data['email'] - admin = data['admin'] + # Get user by email + query_user = get_user(data['email']) - query_user = get_user(email) - - query_user.admin = admin + # Update user admin state + query_user.admin = data['admin'] db.session.commit() return make_response({}, 200, "Successfully updated users admin rights") @@ -163,9 +170,11 @@ def set_admin(data): def set_cron(data): email = get_email_or_abort_401() + # Check if required data is available if not check_if_cron_data_exists(data): abort(400, "Cron data missing") + # Update user cron get_user(email).cron = data['cron'] db.session.commit() @@ -178,18 +187,22 @@ def set_cron(data): @users_blueprint.auth_required(auth) @users_blueprint.doc(summary="Delete user", description="Deletes user by username") def delete_user(data): + # Check if required data is available if not check_if_email_data_exists(data): abort(400, "Email missing") - email = data['email'] - - if email == get_email_or_abort_401(): # Username is same as current user - db.session.query(User).filter_by(email=email).delete() + # Check if email to delete is current user + # -> if, delete user + # -> if not, check if user is admin + # -> if, delete user + # -> else, abort + if data['email'] == get_email_or_abort_401(): # Username is same as current user + db.session.query(User).filter_by(email=data['email']).delete() db.session.commit() - else: # Delete different user than my user -> only admin users + else: abort_if_no_admin() - db.session.query(User).filter_by(email=email).delete() + db.session.query(User).filter_by(email=data['email']).delete() db.session.commit() return make_response({}, 200, "Successfully removed user") diff --git a/api/app/helper_functions.py b/api/app/helper_functions.py index d3edea3..560f114 100644 --- a/api/app/helper_functions.py +++ b/api/app/helper_functions.py @@ -14,28 +14,48 @@ from flask import request, jsonify def hash_password(password): + """ + Hash plain password to save it in the database + """ return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) def check_password(hashed_password, user_password): + """ + Check if the password is correct using the bcrypt checkpw function + """ return bcrypt.checkpw(user_password, hashed_password) def get_email_from_token_data(token): + """ + Extract email from token data + """ + + # If token is not provided-> return None if token is None or len(token) < 2: return None else: + # Token contains "Bearer " -> remove it token = token[1] + # Again: Check if token is not None + # Don't know why, but sometimes the token is None if token is not None: - if ':' in token: # Maybe bot token, check if token valid and return username after ":" then + + # We decided to append the user id to the bearer token using ":" as separator to select an specific user + # If the token contains ":" -> It may be a bot token + # If token valid -> return user email, not bot email + if ':' in token: telegram_user_id = token.split(":")[1] token = token.split(":")[0] try: + # Only allow selecting users with telegram_user_id if current user is the bot user if jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] == current_app.config['BOT_EMAIL']: res = db.session.query(User).filter_by(telegram_user_id=telegram_user_id).first() + # Check if user id exists if res is not None: return res.as_dict()['email'] else: @@ -47,12 +67,18 @@ def get_email_from_token_data(token): else: # "Normal" token, extract username from token try: + # Return email from token if token is valid return jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=["HS256"])['email'] except jwt.PyJWTError: return None def get_token(): + """ + Extract token from Authorization header + """ + + # Check if Authorization header is provided if 'Authorization' in request.headers: return request.headers['Authorization'].split(" ") else: @@ -60,7 +86,10 @@ def get_token(): def get_email_or_abort_401(): - # get username from jwt token + """ + Try to receive email from token data + If email is not provided -> abort 401 + """ email = get_email_from_token_data(get_token()) if email is None: # If token not provided or invalid -> return 401 code @@ -70,24 +99,38 @@ def get_email_or_abort_401(): def abort_if_no_admin(): + """ + Check if user is admin + If not -> abort 401 + """ if not is_user_admin(): abort(401, message="Only admin users can access this") def is_user_admin(): + """ + Return users admin status + """ email = get_email_or_abort_401() return db.session.query(User).filter_by(email=email).first().admin def make_response(data, status=200, text=""): + """ + Generate response object + """ return jsonify({"status": status, "text": text, "data": data}) def get_user(email): + """ + Get user from database + """ query_user = db.session.query(User).filter_by(email=email).first() - if query_user is None: # Username doesn't exist + # Check if user exists + if query_user is None: abort(500, message="Can't find user") return query_user diff --git a/api/app/schema.py b/api/app/schema.py index f48f232..134c401 100644 --- a/api/app/schema.py +++ b/api/app/schema.py @@ -22,6 +22,7 @@ class UsersSchema(Schema): username = String() telegram_user_id = String() email = Email() + cron = String() class AdminDataSchema(Schema): diff --git a/api/generate_sample_transactions.py b/api/generate_sample_transactions.py index 3c28db4..b891a34 100644 --- a/api/generate_sample_transactions.py +++ b/api/generate_sample_transactions.py @@ -10,17 +10,17 @@ import random import faker import requests -username = '' -password = '' +username = 'bot@example.com' +password = 'bot' shares = ["TWTR", "GOOG", "AAPL", "MSFT", "AMZN", "FB", "NFLX", "TSLA", "BABA", "BA", "BAC", "C", "CAT", "CSCO", "CVX", "DIS", "DOW", "DUK", "GE", "HD", "IBM" "INTC", "JNJ", "JPM", "KO", "MCD", "MMM", "MRK", "NKE", "PFE", "PG", "T", "UNH", "UTX", "V", "VZ", "WMT", "XOM", "YHOO", "ZTS"] fake = faker.Faker() -token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": username, "password": password}).json()['data']['token'] +token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": username, "password": password}).json()['data']['token'] + ":1709356058" -for i in range(1, 1000): +for i in range(1, 10): payload = { "count": random.randint(1, 100), "price": random.random() * 100, From 489cd4c5b285b92de77de7f6129c13e684d7f2ea Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 12 Apr 2022 12:16:25 +0200 Subject: [PATCH 180/263] Removed code --- api/generate_sample_transactions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/generate_sample_transactions.py b/api/generate_sample_transactions.py index b891a34..2c38856 100644 --- a/api/generate_sample_transactions.py +++ b/api/generate_sample_transactions.py @@ -10,15 +10,15 @@ import random import faker import requests -username = 'bot@example.com' -password = 'bot' +username = '' +password = '' shares = ["TWTR", "GOOG", "AAPL", "MSFT", "AMZN", "FB", "NFLX", "TSLA", "BABA", "BA", "BAC", "C", "CAT", "CSCO", "CVX", "DIS", "DOW", "DUK", "GE", "HD", "IBM" "INTC", "JNJ", "JPM", "KO", "MCD", "MMM", "MRK", "NKE", "PFE", "PG", "T", "UNH", "UTX", "V", "VZ", "WMT", "XOM", "YHOO", "ZTS"] fake = faker.Faker() -token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": username, "password": password}).json()['data']['token'] + ":1709356058" +token = requests.post(os.getenv("API_URL") + '/user/login', json={"email": username, "password": password}).json()['data']['token'] for i in range(1, 10): payload = { From 564fa5f308921d3cabb45c2838452df4e10d7f8e Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 12 Apr 2022 12:40:42 +0200 Subject: [PATCH 181/263] Added watchtower to automatically update containers --- deploy/base/docker-compose.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/deploy/base/docker-compose.yml b/deploy/base/docker-compose.yml index 0782888..0191ffa 100644 --- a/deploy/base/docker-compose.yml +++ b/deploy/base/docker-compose.yml @@ -64,6 +64,14 @@ services: - portainer_data:/data - /var/run/docker.sock:/var/run/docker.sock + watchtower: + image: containrrr/watchtower + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /etc/localtime:/etc/localtime:ro + env_file: + - ${PWD}/.env + networks: default: external: From 0eac07e6f548cffdf4db6e05bdfe569dd137b36a Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 12 Apr 2022 12:41:08 +0200 Subject: [PATCH 182/263] Added watchtower config --- deploy/base/.env | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 deploy/base/.env diff --git a/deploy/base/.env b/deploy/base/.env new file mode 100644 index 0000000..932c2e2 --- /dev/null +++ b/deploy/base/.env @@ -0,0 +1,3 @@ +WATCHTOWER_SCHEDULE=0 5 3 * * * +WATCHTOWER_ROLLING_RESTART=true +WATCHTOWER_CLEANUP=true \ No newline at end of file From 2a4abb0fd064ac0eed755df4b45b479a23fd25e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Apr 2022 12:52:48 +0000 Subject: [PATCH 183/263] Bump jasmine-core from 4.0.1 to 4.1.0 in /frontend Bumps [jasmine-core](https://github.com/jasmine/jasmine) from 4.0.1 to 4.1.0. - [Release notes](https://github.com/jasmine/jasmine/releases) - [Changelog](https://github.com/jasmine/jasmine/blob/main/RELEASE.md) - [Commits](https://github.com/jasmine/jasmine/compare/v4.0.1...v4.1.0) --- updated-dependencies: - dependency-name: jasmine-core dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 14 +++++++------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2cc45ca..d0c34c1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -29,7 +29,7 @@ "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.2", "@types/node": "^17.0.23", - "jasmine-core": "~4.0.0", + "jasmine-core": "~4.1.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", @@ -6958,9 +6958,9 @@ } }, "node_modules/jasmine-core": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.0.1.tgz", - "integrity": "sha512-w+JDABxQCkxbGGxg+a2hUVZyqUS2JKngvIyLGu/xiw2ZwgsoSB0iiecLQsQORSeaKQ6iGrCyWG86RfNDuoA7Lg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.1.0.tgz", + "integrity": "sha512-8E8BiffCL8sBwK1zU9cbavLe8xpJAgOduSJ6N8PJVv8VosQ/nxVTuXj2kUeHxTlZBVvh24G19ga7xdiaxlceKg==", "dev": true }, "node_modules/jest-worker": { @@ -16612,9 +16612,9 @@ } }, "jasmine-core": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.0.1.tgz", - "integrity": "sha512-w+JDABxQCkxbGGxg+a2hUVZyqUS2JKngvIyLGu/xiw2ZwgsoSB0iiecLQsQORSeaKQ6iGrCyWG86RfNDuoA7Lg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.1.0.tgz", + "integrity": "sha512-8E8BiffCL8sBwK1zU9cbavLe8xpJAgOduSJ6N8PJVv8VosQ/nxVTuXj2kUeHxTlZBVvh24G19ga7xdiaxlceKg==", "dev": true }, "jest-worker": { diff --git a/frontend/package.json b/frontend/package.json index d31fa67..ed43eea 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,7 +31,7 @@ "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.2", "@types/node": "^17.0.23", - "jasmine-core": "~4.0.0", + "jasmine-core": "~4.1.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", From 1e752b60a333c77fc8b191d51d4185e13b83c0b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Apr 2022 09:38:45 +0000 Subject: [PATCH 184/263] Bump karma from 6.3.17 to 6.3.18 in /frontend Bumps [karma](https://github.com/karma-runner/karma) from 6.3.17 to 6.3.18. - [Release notes](https://github.com/karma-runner/karma/releases) - [Changelog](https://github.com/karma-runner/karma/blob/master/CHANGELOG.md) - [Commits](https://github.com/karma-runner/karma/compare/v6.3.17...v6.3.18) --- updated-dependencies: - dependency-name: karma dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 18 +++++++++--------- frontend/package.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2cc45ca..8a053b1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -30,7 +30,7 @@ "@types/jasmine": "~4.0.2", "@types/node": "^17.0.23", "jasmine-core": "~4.0.0", - "karma": "~6.3.0", + "karma": "~6.3.18", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~4.0.0", @@ -7093,9 +7093,9 @@ ] }, "node_modules/karma": { - "version": "6.3.17", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.17.tgz", - "integrity": "sha512-2TfjHwrRExC8yHoWlPBULyaLwAFmXmxQrcuFImt/JsAsSZu1uOWTZ1ZsWjqQtWpHLiatJOHL5jFjXSJIgCd01g==", + "version": "6.3.18", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.18.tgz", + "integrity": "sha512-YEwXVHRILKWKN7uEW9IkgTPjnYGb3YA3MDvlp04xpSRAyrNPoRmsBayLDgHykKAwBm6/mAOckj4xi/1JdQfhzQ==", "dev": true, "dependencies": { "@colors/colors": "1.5.0", @@ -7117,7 +7117,7 @@ "qjobs": "^1.2.0", "range-parser": "^1.2.1", "rimraf": "^3.0.2", - "socket.io": "^4.2.0", + "socket.io": "^4.4.1", "source-map": "^0.6.1", "tmp": "^0.2.1", "ua-parser-js": "^0.7.30", @@ -16717,9 +16717,9 @@ "dev": true }, "karma": { - "version": "6.3.17", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.17.tgz", - "integrity": "sha512-2TfjHwrRExC8yHoWlPBULyaLwAFmXmxQrcuFImt/JsAsSZu1uOWTZ1ZsWjqQtWpHLiatJOHL5jFjXSJIgCd01g==", + "version": "6.3.18", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.18.tgz", + "integrity": "sha512-YEwXVHRILKWKN7uEW9IkgTPjnYGb3YA3MDvlp04xpSRAyrNPoRmsBayLDgHykKAwBm6/mAOckj4xi/1JdQfhzQ==", "dev": true, "requires": { "@colors/colors": "1.5.0", @@ -16741,7 +16741,7 @@ "qjobs": "^1.2.0", "range-parser": "^1.2.1", "rimraf": "^3.0.2", - "socket.io": "^4.2.0", + "socket.io": "^4.4.1", "source-map": "^0.6.1", "tmp": "^0.2.1", "ua-parser-js": "^0.7.30", diff --git a/frontend/package.json b/frontend/package.json index d31fa67..abf2e9f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -32,7 +32,7 @@ "@types/jasmine": "~4.0.2", "@types/node": "^17.0.23", "jasmine-core": "~4.0.0", - "karma": "~6.3.0", + "karma": "~6.3.18", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~4.0.0", From 29c6b2bcf7d2f8de1165f024bf38a95ffc8a1ecc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Apr 2022 09:39:22 +0000 Subject: [PATCH 185/263] Bump karma-jasmine from 4.0.1 to 5.0.0 in /frontend Bumps [karma-jasmine](https://github.com/karma-runner/karma-jasmine) from 4.0.1 to 5.0.0. - [Release notes](https://github.com/karma-runner/karma-jasmine/releases) - [Changelog](https://github.com/karma-runner/karma-jasmine/blob/master/CHANGELOG.md) - [Commits](https://github.com/karma-runner/karma-jasmine/compare/v4.0.1...v5.0.0) --- updated-dependencies: - dependency-name: karma-jasmine dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 34 +++++++++++++++++----------------- frontend/package.json | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2cc45ca..42501bb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -33,7 +33,7 @@ "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", - "karma-jasmine": "~4.0.0", + "karma-jasmine": "~5.0.0", "karma-jasmine-html-reporter": "~1.7.0", "typescript": "~4.5.2" } @@ -7157,18 +7157,18 @@ } }, "node_modules/karma-jasmine": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-4.0.1.tgz", - "integrity": "sha512-h8XDAhTiZjJKzfkoO1laMH+zfNlra+dEQHUAjpn5JV1zCPtOIVWGQjLBrqhnzQa/hrU2XrZwSyBa6XjEBzfXzw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.0.0.tgz", + "integrity": "sha512-dsFkCoTwyoNyQnMgegS72wIA/2xPDJG5yzTry0448U6lAY7P60Wgg4UuLlbdLv8YHbimgNpDXjjmfPdc99EDWQ==", "dev": true, "dependencies": { - "jasmine-core": "^3.6.0" + "jasmine-core": "^4.1.0" }, "engines": { - "node": ">= 10" + "node": ">=12" }, "peerDependencies": { - "karma": "*" + "karma": "^6.0.0" } }, "node_modules/karma-jasmine-html-reporter": { @@ -7183,9 +7183,9 @@ } }, "node_modules/karma-jasmine/node_modules/jasmine-core": { - "version": "3.99.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.99.1.tgz", - "integrity": "sha512-Hu1dmuoGcZ7AfyynN3LsfruwMbxMALMka+YtZeGoLuDEySVmVAPaonkNoBRIw/ectu8b9tVQCJNgp4a4knp+tg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.1.0.tgz", + "integrity": "sha512-8E8BiffCL8sBwK1zU9cbavLe8xpJAgOduSJ6N8PJVv8VosQ/nxVTuXj2kUeHxTlZBVvh24G19ga7xdiaxlceKg==", "dev": true }, "node_modules/karma-source-map-support": { @@ -16819,18 +16819,18 @@ } }, "karma-jasmine": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-4.0.1.tgz", - "integrity": "sha512-h8XDAhTiZjJKzfkoO1laMH+zfNlra+dEQHUAjpn5JV1zCPtOIVWGQjLBrqhnzQa/hrU2XrZwSyBa6XjEBzfXzw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.0.0.tgz", + "integrity": "sha512-dsFkCoTwyoNyQnMgegS72wIA/2xPDJG5yzTry0448U6lAY7P60Wgg4UuLlbdLv8YHbimgNpDXjjmfPdc99EDWQ==", "dev": true, "requires": { - "jasmine-core": "^3.6.0" + "jasmine-core": "^4.1.0" }, "dependencies": { "jasmine-core": { - "version": "3.99.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.99.1.tgz", - "integrity": "sha512-Hu1dmuoGcZ7AfyynN3LsfruwMbxMALMka+YtZeGoLuDEySVmVAPaonkNoBRIw/ectu8b9tVQCJNgp4a4knp+tg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.1.0.tgz", + "integrity": "sha512-8E8BiffCL8sBwK1zU9cbavLe8xpJAgOduSJ6N8PJVv8VosQ/nxVTuXj2kUeHxTlZBVvh24G19ga7xdiaxlceKg==", "dev": true } } diff --git a/frontend/package.json b/frontend/package.json index d31fa67..9a3451f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,7 +35,7 @@ "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", - "karma-jasmine": "~4.0.0", + "karma-jasmine": "~5.0.0", "karma-jasmine-html-reporter": "~1.7.0", "typescript": "~4.5.2" } From 79cfe4e64e7f0ae142b45a4cd7ebdc6ee804d536 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Apr 2022 09:23:15 +0000 Subject: [PATCH 186/263] Bump @angular/material from 13.3.2 to 13.3.3 in /frontend Bumps [@angular/material](https://github.com/angular/components) from 13.3.2 to 13.3.3. - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/13.3.2...13.3.3) --- updated-dependencies: - dependency-name: "@angular/material" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 28 ++++++++++++++-------------- frontend/package.json | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2cc45ca..7c0ff5e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,7 +14,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.3.2", + "@angular/material": "^13.3.3", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", @@ -378,9 +378,9 @@ } }, "node_modules/@angular/cdk": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.2.tgz", - "integrity": "sha512-Rb+SvQpjXqa0kedk/6nG57f8dC4uG1q35SR6mt6jCz84ikyS2zhikVbzaxLdG15uDvq1+N5Vx3NTSgH19XUSug==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.3.tgz", + "integrity": "sha512-dzd31mta2VwffxbeO4CelMqb7WswLnkC/r2QZXySnc0CTmj44HqXkqdZuEvVgxaKRVpxsYeuBuhhhy8U00YMOw==", "dependencies": { "tslib": "^2.3.0" }, @@ -670,15 +670,15 @@ } }, "node_modules/@angular/material": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.2.tgz", - "integrity": "sha512-agIuNcN1wO05ODEXc0wwrMK2ydnhPGO8tcBBCJXrDgiA/ktwm+WtfOsiZwPoev7aJ8pactVhxT5R0bJ6vW2SmA==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.3.tgz", + "integrity": "sha512-PkZ7VW3/7tqdBHSOOmq7com7Ir147OEe1+kfgF/G3y8WUutI9jY2cHKARXGWB+5WgcqKr7ol43u239UGkPfFHg==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "^13.0.0 || ^14.0.0-0", - "@angular/cdk": "13.3.2", + "@angular/cdk": "13.3.3", "@angular/common": "^13.0.0 || ^14.0.0-0", "@angular/core": "^13.0.0 || ^14.0.0-0", "@angular/forms": "^13.0.0 || ^14.0.0-0", @@ -11789,9 +11789,9 @@ } }, "@angular/cdk": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.2.tgz", - "integrity": "sha512-Rb+SvQpjXqa0kedk/6nG57f8dC4uG1q35SR6mt6jCz84ikyS2zhikVbzaxLdG15uDvq1+N5Vx3NTSgH19XUSug==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.3.tgz", + "integrity": "sha512-dzd31mta2VwffxbeO4CelMqb7WswLnkC/r2QZXySnc0CTmj44HqXkqdZuEvVgxaKRVpxsYeuBuhhhy8U00YMOw==", "requires": { "parse5": "^5.0.0", "tslib": "^2.3.0" @@ -11992,9 +11992,9 @@ } }, "@angular/material": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.2.tgz", - "integrity": "sha512-agIuNcN1wO05ODEXc0wwrMK2ydnhPGO8tcBBCJXrDgiA/ktwm+WtfOsiZwPoev7aJ8pactVhxT5R0bJ6vW2SmA==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.3.tgz", + "integrity": "sha512-PkZ7VW3/7tqdBHSOOmq7com7Ir147OEe1+kfgF/G3y8WUutI9jY2cHKARXGWB+5WgcqKr7ol43u239UGkPfFHg==", "requires": { "tslib": "^2.3.0" } diff --git a/frontend/package.json b/frontend/package.json index d31fa67..02d8871 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.3.2", + "@angular/material": "^13.3.3", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", From 3992e77d2b3539115a7098c2f92f089d74e792a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Apr 2022 09:24:02 +0000 Subject: [PATCH 187/263] Bump @angular-devkit/build-angular from 13.3.1 to 13.3.3 in /frontend Bumps [@angular-devkit/build-angular](https://github.com/angular/angular-cli) from 13.3.1 to 13.3.3. - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/13.3.1...13.3.3) --- updated-dependencies: - dependency-name: "@angular-devkit/build-angular" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 304 +++++++++++++++++++------------------ frontend/package.json | 2 +- 2 files changed, 155 insertions(+), 151 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2cc45ca..86e73af 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,7 +24,7 @@ "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.3.1", + "@angular-devkit/build-angular": "~13.3.3", "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.2", @@ -51,16 +51,49 @@ "node": ">=6.0.0" } }, + "node_modules/@angular-devkit/architect": { + "version": "0.1303.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.3.tgz", + "integrity": "sha512-WRVVBCzLlMqRZVhZXGASHzNJK/OCAvl/DTGhlLuJDIjF7lVGnXHjtwNM8ilYZq949OnC3fly5Z61TfhbN/OHCg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.3", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/architect/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/architect/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/@angular-devkit/build-angular": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.1.tgz", - "integrity": "sha512-xxBW4zZZM+lewW0nEpk9SXw6BMYhxe8WI/FjyEroOV8G2IuOrjZ4112QOpk6jCgmPHSOEldbltEdwoVLAnu09Q==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.3.tgz", + "integrity": "sha512-iEpNF3tF+9Gw+qQKL63fPFHIvWokJdrgVU4GzENQ5QeL8zk8iYTEbH3jWogq5tWy5+VmNP/mKkasq9i78lRiYw==", "dev": true, "dependencies": { "@ampproject/remapping": "1.1.1", - "@angular-devkit/architect": "0.1303.1", - "@angular-devkit/build-webpack": "0.1303.1", - "@angular-devkit/core": "13.3.1", + "@angular-devkit/architect": "0.1303.3", + "@angular-devkit/build-webpack": "0.1303.3", + "@angular-devkit/core": "13.3.3", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -71,7 +104,7 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.3.1", + "@ngtools/webpack": "13.3.3", "ansi-colors": "4.1.1", "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", @@ -93,7 +126,7 @@ "license-webpack-plugin": "4.0.2", "loader-utils": "3.2.0", "mini-css-extract-plugin": "2.5.3", - "minimatch": "3.0.4", + "minimatch": "3.0.5", "open": "8.4.0", "ora": "5.4.1", "parse5-html-rewriting-stream": "6.0.1", @@ -161,48 +194,6 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { - "version": "0.1303.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", - "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "13.3.1", - "rxjs": "6.6.7" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", - "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", - "dev": true, - "dependencies": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -222,12 +213,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1303.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.1.tgz", - "integrity": "sha512-KSnR3y2q5hxh7t7ZSi0Emv/Kh9+D105JaEeyEqjqRjLdZSd2m6eAxbSUMNOAsbqnJTMCfzU5AG7jhbujuge0dQ==", + "version": "0.1303.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.3.tgz", + "integrity": "sha512-v/z/YgwrAzYn1LfN9OHNxqcThyyg4LLx28hmHzDs5gyDShAK189y34EoT9uQ+lCyQrPVhP7UKACCxCdSwOEJiA==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1303.1", + "@angular-devkit/architect": "0.1303.3", "rxjs": "6.6.7" }, "engines": { @@ -240,25 +231,28 @@ "webpack-dev-server": "^4.0.0" } }, - "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { - "version": "0.1303.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", - "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.3.1", - "rxjs": "6.6.7" + "tslib": "^1.9.0" }, "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "npm": ">=2.0.0" } }, - "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", - "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "node_modules/@angular-devkit/build-webpack/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-devkit/core": { + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.3.tgz", + "integrity": "sha512-lfQwY9LuVRwcNVzGmyPcwOpb3CAobP4T+c3joR1LLIPS5lzcM0oeCE2bon9N52Ktn4Q/pH98dVtjWL+jSrUADw==", "dev": true, "dependencies": { "ajv": "8.9.0", @@ -282,7 +276,7 @@ } } }, - "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { + "node_modules/@angular-devkit/core/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", @@ -294,7 +288,7 @@ "npm": ">=2.0.0" } }, - "node_modules/@angular-devkit/build-webpack/node_modules/tslib": { + "node_modules/@angular-devkit/core/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", @@ -2499,9 +2493,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.1.tgz", - "integrity": "sha512-40iEqAA/l882MPbGuG5EYxzsPWJ37fT4fF22SkPLX2eBgNhJ4K8XMt0gqcFhkHUsSe63frg1qjQ1Pd31msu0bQ==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.3.tgz", + "integrity": "sha512-O6EzafKfFuvI3Ju941u7ANs0mT7YDdChbVRhVECCPWOTm3Klr73js3bnCDzaJlxZNjzlG/KeUu5ghrhbMrHjSw==", "dev": true, "engines": { "node": "^12.20.0 || ^14.15.0 || >=16.10.0", @@ -3372,9 +3366,9 @@ } }, "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "dependencies": { "lodash": "^4.17.14" @@ -7768,9 +7762,9 @@ "dev": true }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -9660,9 +9654,9 @@ "dev": true }, "node_modules/regexp.prototype.flags": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", - "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.2.tgz", + "integrity": "sha512-Ynz8fTQW5/1elh+jWU2EDDzeoNbD0OQ0R+D1VJU5ATOkUaro4A9YEkdN2ODQl/8UQFPPpZNw91fOcLFamM7Pww==", "dev": true, "dependencies": { "call-bind": "^1.0.2", @@ -11569,16 +11563,43 @@ "sourcemap-codec": "1.4.8" } }, + "@angular-devkit/architect": { + "version": "0.1303.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.3.tgz", + "integrity": "sha512-WRVVBCzLlMqRZVhZXGASHzNJK/OCAvl/DTGhlLuJDIjF7lVGnXHjtwNM8ilYZq949OnC3fly5Z61TfhbN/OHCg==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.3", + "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, "@angular-devkit/build-angular": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.1.tgz", - "integrity": "sha512-xxBW4zZZM+lewW0nEpk9SXw6BMYhxe8WI/FjyEroOV8G2IuOrjZ4112QOpk6jCgmPHSOEldbltEdwoVLAnu09Q==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.3.tgz", + "integrity": "sha512-iEpNF3tF+9Gw+qQKL63fPFHIvWokJdrgVU4GzENQ5QeL8zk8iYTEbH3jWogq5tWy5+VmNP/mKkasq9i78lRiYw==", "dev": true, "requires": { "@ampproject/remapping": "1.1.1", - "@angular-devkit/architect": "0.1303.1", - "@angular-devkit/build-webpack": "0.1303.1", - "@angular-devkit/core": "13.3.1", + "@angular-devkit/architect": "0.1303.3", + "@angular-devkit/build-webpack": "0.1303.3", + "@angular-devkit/core": "13.3.3", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -11589,7 +11610,7 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.3.1", + "@ngtools/webpack": "13.3.3", "ansi-colors": "4.1.1", "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", @@ -11612,7 +11633,7 @@ "license-webpack-plugin": "4.0.2", "loader-utils": "3.2.0", "mini-css-extract-plugin": "2.5.3", - "minimatch": "3.0.4", + "minimatch": "3.0.5", "open": "8.4.0", "ora": "5.4.1", "parse5-html-rewriting-stream": "6.0.1", @@ -11642,30 +11663,6 @@ "webpack-subresource-integrity": "5.1.0" }, "dependencies": { - "@angular-devkit/architect": { - "version": "0.1303.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", - "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", - "dev": true, - "requires": { - "@angular-devkit/core": "13.3.1", - "rxjs": "6.6.7" - } - }, - "@angular-devkit/core": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", - "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - } - }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -11686,39 +11683,46 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.1303.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.1.tgz", - "integrity": "sha512-KSnR3y2q5hxh7t7ZSi0Emv/Kh9+D105JaEeyEqjqRjLdZSd2m6eAxbSUMNOAsbqnJTMCfzU5AG7jhbujuge0dQ==", + "version": "0.1303.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.3.tgz", + "integrity": "sha512-v/z/YgwrAzYn1LfN9OHNxqcThyyg4LLx28hmHzDs5gyDShAK189y34EoT9uQ+lCyQrPVhP7UKACCxCdSwOEJiA==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1303.1", + "@angular-devkit/architect": "0.1303.3", "rxjs": "6.6.7" }, "dependencies": { - "@angular-devkit/architect": { - "version": "0.1303.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", - "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, "requires": { - "@angular-devkit/core": "13.3.1", - "rxjs": "6.6.7" - } - }, - "@angular-devkit/core": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", - "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" + "tslib": "^1.9.0" } }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular-devkit/core": { + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.3.tgz", + "integrity": "sha512-lfQwY9LuVRwcNVzGmyPcwOpb3CAobP4T+c3joR1LLIPS5lzcM0oeCE2bon9N52Ktn4Q/pH98dVtjWL+jSrUADw==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "dependencies": { "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -13266,9 +13270,9 @@ } }, "@ngtools/webpack": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.1.tgz", - "integrity": "sha512-40iEqAA/l882MPbGuG5EYxzsPWJ37fT4fF22SkPLX2eBgNhJ4K8XMt0gqcFhkHUsSe63frg1qjQ1Pd31msu0bQ==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.3.tgz", + "integrity": "sha512-O6EzafKfFuvI3Ju941u7ANs0mT7YDdChbVRhVECCPWOTm3Klr73js3bnCDzaJlxZNjzlG/KeUu5ghrhbMrHjSw==", "dev": true, "requires": {} }, @@ -14007,9 +14011,9 @@ "dev": true }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "requires": { "lodash": "^4.17.14" @@ -17218,9 +17222,9 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -18609,9 +18613,9 @@ "dev": true }, "regexp.prototype.flags": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", - "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.2.tgz", + "integrity": "sha512-Ynz8fTQW5/1elh+jWU2EDDzeoNbD0OQ0R+D1VJU5ATOkUaro4A9YEkdN2ODQl/8UQFPPpZNw91fOcLFamM7Pww==", "dev": true, "requires": { "call-bind": "^1.0.2", diff --git a/frontend/package.json b/frontend/package.json index d31fa67..b3ff57c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,7 +26,7 @@ "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.3.1", + "@angular-devkit/build-angular": "~13.3.3", "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.2", From acdd46780c27991247dc55a391bcf1e73c35fefa Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Sun, 17 Apr 2022 10:49:42 +0200 Subject: [PATCH 188/263] added small error handling --- telegram_bot/bot_scheduler.py | 37 +++++++++++++++++++++++++++++++ telegram_bot/news/news_fetcher.py | 10 ++++++--- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/telegram_bot/bot_scheduler.py b/telegram_bot/bot_scheduler.py index e69de29..8fcef6f 100644 --- a/telegram_bot/bot_scheduler.py +++ b/telegram_bot/bot_scheduler.py @@ -0,0 +1,37 @@ +""" +script for regularly sending updates on shares and news based on user interval +""" +__author__ = "Florian Kellermann, Linus Eickhoff" +__date__ = "05.04.2022" +__version__ = "0.0.1" +__license__ = "None" + +import shares.share_fetcher as share_fetcher +import news.news_fetcher as news_fetcher + +import datetime +import sys +from apscheduler.schedulers.blocking import BlockingScheduler + +''' +* * * * * code +┬ ┬ ┬ ┬ ┬ +│ │ │ │ │ +│ │ │ │ └──── weekday (0->Monday, 7->Sunday) +│ │ │ └────── Month (1-12) +│ │ └──────── Day (1-31) +│ └────────── Hour (0-23) +└──────────── Minute (0-59) + +example 0 8 * * * -> daily update at 8am +''' + +def user_updates(): + """sends timed updates automatically to user + + Args: + + Returns: + + """ + return \ No newline at end of file diff --git a/telegram_bot/news/news_fetcher.py b/telegram_bot/news/news_fetcher.py index 2b79721..96f50ff 100644 --- a/telegram_bot/news/news_fetcher.py +++ b/telegram_bot/news/news_fetcher.py @@ -20,9 +20,13 @@ load_dotenv() # Init api_key = os.getenv('NEWS_API_KEY') newsapi = NewsApiClient(api_key=api_key) -source_json = requests.get(f"https://newsapi.org/v2/top-headlines/sources?apiKey={api_key}&language=en").json() -sources = source_json["sources"] -str_sources = ",".join([source["id"] for source in sources]) +try: + source_json = requests.get(f"https://newsapi.org/v2/top-headlines/sources?apiKey={api_key}&language=en").json() + sources = source_json["sources"] + str_sources = ",".join([source["id"] for source in sources]) +except KeyError: + print("Error: Could not get sources") + sys.exit(1) def get_all_news_by_keyword(keyword, from_date="2000-01-01"): From 5078660ce19bbb15f83c9b4dec8442b257aee96c Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Sun, 17 Apr 2022 11:21:49 +0200 Subject: [PATCH 189/263] fixed dict errors --- telegram_bot/api_handling/api_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index b1ce853..fa4a37e 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -260,7 +260,7 @@ class API_Handler: req = s.get(self.db_adress + "/transactions", headers=headers) if req.status_code == 200: - transactions_dict = dict(req.json()["data"]) + transactions_dict = req.json()["data"] return transactions_dict else: return self.get_user_transactions(user_id, max_retries-1) @@ -303,7 +303,7 @@ class API_Handler: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} req = s.get(self.db_adress + "/portfolio", headers=headers) if req.status_code == 200: - portfolio_dict = dict(req.json()["data"]) + portfolio_dict = req.json()["data"] return portfolio_dict else: return self.get_user_portfolio(user_id, max_retries-1) From 543c51397e64fd1808e2e7b69d934fab8dfae10d Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Sun, 17 Apr 2022 11:57:28 +0200 Subject: [PATCH 190/263] added transaction and portfolio commands --- telegram_bot/api_handling/api_handler.py | 4 ++ telegram_bot/bot.py | 64 +++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index fa4a37e..c06e6ee 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -33,6 +33,10 @@ class API_Handler: get_user_transactions(user_id): gets the transactions of the user set_transaction(user_id, transaction): sets the transaction of the user delete_transaction(user_id, transaction): deletes the transaction of the user + get_user_portfolio(user_id): gets the portfolio of the user + set_portfolio(user_id, portfolio): sets the portfolio of the user + delete_portfolio(user_id, portfolio): deletes the portfolio of the user + set_cron_interval(user_id, interval): sets the cron interval of the user """ diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 83582ba..b1d985b 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -14,14 +14,13 @@ __license__ = "None" # API Documentation https://core.telegram.org/bots/api # Code examples https://github.com/eternnoir/pyTelegramBotAPI#getting-started -from ast import keyword import os import telebot -import time import sys import logging import json +import re import news.news_fetcher as news import shares.share_fetcher as share_fetcher @@ -380,6 +379,67 @@ def send_keywords(message): bot.send_message(chat_id=user_id, text=f'Your keywords are: _{keywords_str}_', parse_mode="MARKDOWN") +@bot.message_handler(commands=['portfolio']) +def send_portfolio(message): + """ Send portfolio of user + :type message: message object bot + :param message: message that was reacted to, in this case always '/portfolio' + + :raises: none + + :rtype: none + """ + user_id = int(message.from_user.id) + portfolio = api_handler.get_user_portfolio(user_id) + if portfolio == None: + bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure too connect your telegram id (/id) on https://gruppe1.testsites.info') + return + if not portfolio: + bot.send_message(chat_id=user_id, text='You do not have any stocks in your portfolio.') + return + else: + portfolio_str = ', '.join(portfolio) # tbd: format portfolio + bot.send_message(chat_id=user_id, text=f'Your portfolio is: _{portfolio_str}_', parse_mode="MARKDOWN") + + +@bot.message_handler(commands=['newtransaction']) +def set_new_transaction(message): + """ Set new transaction for user + :type message: message object bot + :param message: message that was reacted to, in this case always '/newtransaction' + + :raises: none + + :rtype: none + """ + user_id = int(message.from_user.id) + bot.send_message(chat_id=user_id, text='Type ",," (time of transaction will be set to now):') + bot.register_next_step_handler(message, set_new_transaction_step) + +def set_new_transaction_step(message): + user_id = int(message.from_user.id) + + if not re.match(r"[A-Za-z0-9]+,[0-9]+[.[0-9]+]?,[0-9]+[.[0-9]+]?", message.text): + bot.send_message(chat_id=user_id, text='Invalid format. Try again with /newtransaction.') + return + + transaction_data = str(message.text).split(',') + symbol = str(transaction_data[0]) + amount = float(transaction_data[1]) + price = float(transaction_data[2]) + time = dt.datetime.now() + + status = api_handler.set_transaction(user_id, amount, price, symbol, time) + + if status == 200: + bot.send_message(chat_id=user_id, text='Transaction succesfully added.') + else: + bot.send_message(chat_id=user_id, text=f'Failed adding transaction. (statuscode {status})') + + + + + @bot.message_handler(func=lambda message: True) # Returning that command is unknown for any other statement def echo_all(message): From b06b02432680a642cc06016a6ac3ff11f2cdd753 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Sun, 17 Apr 2022 12:20:53 +0200 Subject: [PATCH 191/263] bot new commands and funcs + api updates --- telegram_bot/api_handling/api_handler.py | 6 ++- telegram_bot/bot.py | 49 ++++++++++++++++++++++++ telegram_bot/requirements.txt | 1 + 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index c06e6ee..c5bc997 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -12,6 +12,7 @@ __license__ = "None" import sys import os import requests as r +from croniter import croniter @@ -32,7 +33,6 @@ class API_Handler: delete_share(user_id, symbol): deletes the share of the user get_user_transactions(user_id): gets the transactions of the user set_transaction(user_id, transaction): sets the transaction of the user - delete_transaction(user_id, transaction): deletes the transaction of the user get_user_portfolio(user_id): gets the portfolio of the user set_portfolio(user_id, portfolio): sets the portfolio of the user delete_portfolio(user_id, portfolio): deletes the portfolio of the user @@ -322,6 +322,10 @@ class API_Handler: Returns: int: status code """ + if not croniter.is_valid(cron_interval): + print("Error: Invalid cron format") + return -1 + with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} req = s.put(self.db_adress + "/user/setCron", json={"cron": cron_interval}, headers=headers) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index b1d985b..e2cb748 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -437,7 +437,56 @@ def set_new_transaction_step(message): bot.send_message(chat_id=user_id, text=f'Failed adding transaction. (statuscode {status})') +@bot.message_handler(commands=['interval']) +def send_interval(message): + """ send interval for user + :type message: message object bot + :param message: message that was reacted to, in this case always '/interval' + :raises: none + + :rtype: none + """ + user_id = int(message.from_user.id) + interval = api_handler.get_user(user_id)['cron'] + if interval == None: + bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure too connect your telegram id (/id) on https://gruppe1.testsites.info and set an interval with /setinterval') + return + else: + interval = str(interval) + formatted_interval = str(interval).replace(' ', '_') + bot.send_message(chat_id=user_id, text=f'Your update interval: {interval} (https://crontab.guru/#{formatted_interval})', parse_mode="MARKDOWN") + + +@bot.message_handler(commands=['setinterval']) +def set_new_interval(message): + """ Set new interval for user + :type message: message object bot + :param message: message that was reacted to, in this case always '/setinterval' + + :raises: none + + :rtype: none + """ + user_id = int(message.from_user.id) + bot.send_message(chat_id=user_id, text='Type interval in cron format (https://crontab.guru/):') + bot.register_next_step_handler(message, set_new_interval_step) + +def set_new_interval_step(message): + + user_id = int(message.from_user.id) + interval = str(message.text) + status = api_handler.set_cron_interval(user_id, interval) + + if status == 200: + bot.send_message(chat_id=user_id, text='Interval succesfully set.') + return + + if status == -1: + bot.send_message(chat_id=user_id, text='Invalid interval. Try again with /setinterval.') + return + else: + bot.send_message(chat_id=user_id, text=f'Failed setting interval. (statuscode {status})') @bot.message_handler(func=lambda message: True) # Returning that command is unknown for any other statement diff --git a/telegram_bot/requirements.txt b/telegram_bot/requirements.txt index 2e8462d..146877f 100644 --- a/telegram_bot/requirements.txt +++ b/telegram_bot/requirements.txt @@ -5,3 +5,4 @@ newsapi-python~=0.2.6 python-dotenv~=0.20.0 requests~=2.27.1 APScheduler~=3.9.1 +cronitor~=1.3.4 From caa90a577f4b339a0f97af74540f4b3cd3704a91 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Sun, 17 Apr 2022 12:35:59 +0200 Subject: [PATCH 192/263] testing --- telegram_bot/bot.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index e2cb748..7b5e38d 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -115,7 +115,7 @@ def send_welcome(message): :rtype: none """ - bot.reply_to(message, "/id or /auth get your user id\n/update get updates on your shares.\n/users see all users.\n/news get top article for each keyword.\n/allnews get all news (last 7 days)\n/keywords get all your keywords\n/addkeyword add a keyword\n/removekeyword remove a keyword\n/share get price of specific share\n\n_For further details see https://gruppe1.testsites.info _", parse_mode='MARKDOWN') + bot.reply_to(message, "/id or /auth get your user id\n/update get updates on your shares.\n/users see all users.\n/news get top article for each keyword.\n/allnews get all news (last 7 days)\n/keywords get all your keywords\n/addkeyword add a keyword\n/removekeyword remove a keyword\n/share get price of specific share\n/portfolio see own portfolio\n/newtransaction add new transaction\n/interval get update interval\n/setinterval set update interval\n_For further details see https://gruppe1.testsites.info _", parse_mode='MARKDOWN') @bot.message_handler(commands=['users']) @@ -379,7 +379,7 @@ def send_keywords(message): bot.send_message(chat_id=user_id, text=f'Your keywords are: _{keywords_str}_', parse_mode="MARKDOWN") -@bot.message_handler(commands=['portfolio']) +@bot.message_handler(commands=['portfolio']) #tbd def send_portfolio(message): """ Send portfolio of user :type message: message object bot @@ -402,7 +402,7 @@ def send_portfolio(message): bot.send_message(chat_id=user_id, text=f'Your portfolio is: _{portfolio_str}_', parse_mode="MARKDOWN") -@bot.message_handler(commands=['newtransaction']) +@bot.message_handler(commands=['newtransaction']) #tbd def set_new_transaction(message): """ Set new transaction for user :type message: message object bot @@ -437,7 +437,7 @@ def set_new_transaction_step(message): bot.send_message(chat_id=user_id, text=f'Failed adding transaction. (statuscode {status})') -@bot.message_handler(commands=['interval']) +@bot.message_handler(commands=['interval']) #tbd def send_interval(message): """ send interval for user :type message: message object bot @@ -448,12 +448,12 @@ def send_interval(message): :rtype: none """ user_id = int(message.from_user.id) - interval = api_handler.get_user(user_id)['cron'] + interval = api_handler.get_user(user_id) if interval == None: bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure too connect your telegram id (/id) on https://gruppe1.testsites.info and set an interval with /setinterval') return else: - interval = str(interval) + interval = str(interval['cron']) formatted_interval = str(interval).replace(' ', '_') bot.send_message(chat_id=user_id, text=f'Your update interval: {interval} (https://crontab.guru/#{formatted_interval})', parse_mode="MARKDOWN") @@ -469,7 +469,7 @@ def set_new_interval(message): :rtype: none """ user_id = int(message.from_user.id) - bot.send_message(chat_id=user_id, text='Type interval in cron format (https://crontab.guru/):') + bot.send_message(chat_id=user_id, text='Type interval in cron format:\n(https://crontab.guru/)') bot.register_next_step_handler(message, set_new_interval_step) def set_new_interval_step(message): @@ -482,7 +482,7 @@ def set_new_interval_step(message): bot.send_message(chat_id=user_id, text='Interval succesfully set.') return - if status == -1: + if status == -1: # only -1 when interval is invalid bot.send_message(chat_id=user_id, text='Invalid interval. Try again with /setinterval.') return else: From 26b41f2c94576c38980346008996c4e3786d1027 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Sun, 17 Apr 2022 13:07:57 +0200 Subject: [PATCH 193/263] fixing and debugging --- telegram_bot/bot.py | 60 +++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 7b5e38d..c3ab17e 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -115,7 +115,7 @@ def send_welcome(message): :rtype: none """ - bot.reply_to(message, "/id or /auth get your user id\n/update get updates on your shares.\n/users see all users.\n/news get top article for each keyword.\n/allnews get all news (last 7 days)\n/keywords get all your keywords\n/addkeyword add a keyword\n/removekeyword remove a keyword\n/share get price of specific share\n/portfolio see own portfolio\n/newtransaction add new transaction\n/interval get update interval\n/setinterval set update interval\n_For further details see https://gruppe1.testsites.info _", parse_mode='MARKDOWN') + bot.reply_to(message, "/id or /auth get your user id\n/update get updates on your shares.\n/users see all users.\n/me get my user info\n/news get top article for each keyword.\n/allnews get all news (last 7 days)\n/keywords get all your keywords\n/addkeyword add a keyword\n/removekeyword remove a keyword\n/share get price of specific share\n/portfolio see own portfolio\n/newtransaction add new transaction\n/interval get update interval\n/setinterval set update interval\n_For further details see https://gruppe1.testsites.info _", parse_mode='MARKDOWN') @bot.message_handler(commands=['users']) @@ -140,7 +140,26 @@ def send_all_users(message): answer = str(known_user.user_id) + ' : ' + known_user.user_name bot.send_message(chat_id=user_id, text=answer) + +@bot.message_handler(commands=['me']) +def send_user(message): + """ Send user data + :type message: message object bot + :param message: message that was reacted to, in this case always containing '/me' + :raises: none + + :rtype: none + """ + user_id = int(message.from_user.id) + user_data = api_handler.get_user(user_id) # tbd: formatting + if not user_data or user_data == None: + bot.reply_to(message, "This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info") + return + + bot.reply_to(message, 'Your user data:\n' + str(user_data)) + + @bot.message_handler(commands=['id', 'auth']) # /auth or /id -> Authentication with user_id over web tool def send_id(message): @@ -248,7 +267,7 @@ def send_all_news(message): keywords = api_handler.get_user_keywords(user_id) if keywords == None: - bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure too connect your telegram id (/id) on https://gruppe1.testsites.info') + bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info') return if not keywords: @@ -286,7 +305,7 @@ def send_news(message): keywords = api_handler.get_user_keywords(user_id) if keywords == None: - bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure too connect your telegram id (/id) on https://gruppe1.testsites.info') + bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info') return if not keywords: @@ -369,7 +388,7 @@ def send_keywords(message): user_id = int(message.from_user.id) keywords = api_handler.get_user_keywords(user_id) if keywords == None: - bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure too connect your telegram id (/id) on https://gruppe1.testsites.info') + bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info') return if not keywords: bot.send_message(chat_id=user_id, text='No keywords set for this account. Add keywords by using /addkeyword') @@ -392,17 +411,18 @@ def send_portfolio(message): user_id = int(message.from_user.id) portfolio = api_handler.get_user_portfolio(user_id) if portfolio == None: - bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure too connect your telegram id (/id) on https://gruppe1.testsites.info') + bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info') return if not portfolio: bot.send_message(chat_id=user_id, text='You do not have any stocks in your portfolio.') return else: - portfolio_str = ', '.join(portfolio) # tbd: format portfolio - bot.send_message(chat_id=user_id, text=f'Your portfolio is: _{portfolio_str}_', parse_mode="MARKDOWN") + # tbd: format portfolio + + bot.send_message(chat_id=user_id, text=f'Your portfolio is: _{str(portfolio)}_', parse_mode="MARKDOWN") -@bot.message_handler(commands=['newtransaction']) #tbd +@bot.message_handler(commands=['newtransaction']) #tbd not working rn def set_new_transaction(message): """ Set new transaction for user :type message: message object bot @@ -413,14 +433,14 @@ def set_new_transaction(message): :rtype: none """ user_id = int(message.from_user.id) - bot.send_message(chat_id=user_id, text='Type ",," (time of transaction will be set to now):') + bot.send_message(chat_id=user_id, text='Type ",," (time of transaction will be set to now):') bot.register_next_step_handler(message, set_new_transaction_step) def set_new_transaction_step(message): user_id = int(message.from_user.id) - if not re.match(r"[A-Za-z0-9]+,[0-9]+[.[0-9]+]?,[0-9]+[.[0-9]+]?", message.text): - bot.send_message(chat_id=user_id, text='Invalid format. Try again with /newtransaction.') + if not re.match(r"[A-Za-z0-9]+,[0-9]+(.[0-9]+)?,[0-9]+(.[0-9]+)?", message.text): + bot.send_message(chat_id=user_id, text='Invalid format \n(e.g. AAPL,53.2,120.4).\n Try again with /newtransaction.') return transaction_data = str(message.text).split(',') @@ -428,7 +448,8 @@ def set_new_transaction_step(message): amount = float(transaction_data[1]) price = float(transaction_data[2]) time = dt.datetime.now() - + #print("\n\n\n\n\n") + #print(f"{symbol},{amount},{price},{time}") status = api_handler.set_transaction(user_id, amount, price, symbol, time) if status == 200: @@ -448,14 +469,17 @@ def send_interval(message): :rtype: none """ user_id = int(message.from_user.id) - interval = api_handler.get_user(user_id) - if interval == None: - bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure too connect your telegram id (/id) on https://gruppe1.testsites.info and set an interval with /setinterval') + user_data = api_handler.get_user(user_id) + if user_data == None: + bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info and set an interval with /setinterval') return else: - interval = str(interval['cron']) + interval = str(user_data['cron']) + if interval == 'None': + bot.send_message(chat_id=user_id, text='You do not have an interval set. Set one with /setinterval') + return formatted_interval = str(interval).replace(' ', '_') - bot.send_message(chat_id=user_id, text=f'Your update interval: {interval} (https://crontab.guru/#{formatted_interval})', parse_mode="MARKDOWN") + bot.send_message(chat_id=user_id, text=f'Your update interval: {interval} (https://crontab.guru/#{formatted_interval})') @bot.message_handler(commands=['setinterval']) @@ -483,7 +507,7 @@ def set_new_interval_step(message): return if status == -1: # only -1 when interval is invalid - bot.send_message(chat_id=user_id, text='Invalid interval. Try again with /setinterval.') + bot.send_message(chat_id=user_id, text='Invalid interval format. Try again with\n /setinterval.') return else: bot.send_message(chat_id=user_id, text=f'Failed setting interval. (statuscode {status})') From 1803bba222b56ec7e196a5721589b9d91f3bfdb7 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Sun, 17 Apr 2022 13:26:12 +0200 Subject: [PATCH 194/263] Not working fully yet, still problems with scheduler, might try other module later --- telegram_bot/bot_updates.py | 98 ++++++++++++++++------------------- telegram_bot/requirements.txt | 3 +- telegram_bot/testfile.py | 41 +++++++++++++++ 3 files changed, 87 insertions(+), 55 deletions(-) create mode 100644 telegram_bot/testfile.py diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 821de90..7ed1bf2 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -6,12 +6,16 @@ __date__ = "05.04.2022" __version__ = "0.0.1" __license__ = "None" +from calendar import month from shares.share_fetcher import get_share_price import time import datetime from bot import bot import sys from multiprocessing import Process +from apscheduler.schedulers.blocking import BlockingScheduler +from api_handling.api_handler import API_Handler + ''' * * * * * code @@ -25,7 +29,6 @@ from multiprocessing import Process example 0 8 * * * -> daily update at 8am ''' - user_ids = [] user_crontab = [] @@ -37,20 +40,24 @@ def main_loop(): """ current_time_datetime = datetime.datetime.now() + my_handler = API_Handler("https://gruppe1.testsites.info/api", "bot@example.com", "bot") print(time.ctime()) # Debug - p1 = Process(target= update_crontab, args=(current_time_datetime, )) + """p1 = Process(target= update_crontab, args=(current_time_datetime, my_handler )) p1.start() - p3 = Process(target= update_based_on_crontab, args=(current_time_datetime, ) ) + time.sleep(5) + p3 = Process(target= update_based_on_crontab, args=() ) p3.daemon = True p3.start() p1.join() p3.terminate() - p1.terminate() + p1.terminate()""" + + update_crontab(current_time_datetime, my_handler) -def update_crontab(pCurrent_Time): +def update_crontab(pCurrent_Time, p_my_handler): """ Updating crontab lists every hour :type pCurrent_Time: time when starting crontab update :param pCurrent_Time: datetime @@ -60,14 +67,23 @@ def update_crontab(pCurrent_Time): :rtype: none """ - # Update user info now + global user_crontab + global user_ids - print('in update_crontab') + p_my_handler.set_cron_interval(user_id = 1770205310, cron_interval = "50 11 * * *") - user_ids.clear() # Example for me (Florian Kellermann) - user_crontab.clear() - user_ids.append(1770205310) - user_crontab.append("0 8 * * *") + all_users = p_my_handler.get_all_users() + + user_ids = [] + user_crontab = [] + + for element in all_users: + if element["cron"] != '' and element["telegram_user_id"] != '': + user_ids.append(int(element["telegram_user_id"])) + user_crontab.append(str(element["cron"])) + + print(user_ids) + update_based_on_crontab() while True: time_difference = datetime.datetime.now() - pCurrent_Time @@ -76,52 +92,26 @@ def update_crontab(pCurrent_Time): break -def update_based_on_crontab(pCurrent_Time): - current_time_ctime = time.ctime() - ctime_split = str(current_time_ctime).split(' ') +def update_based_on_crontab(): - if ctime_split[0] == "Mon": - current_day = 0 - elif ctime_split[0] == "Tue": - current_day = 1 - elif ctime_split[0] == "Wed": - current_day = 3 - elif ctime_split[0] == "Thu": - current_day = 4 - elif ctime_split[0] == "Fri": - current_day = 5 - elif ctime_split[0] == "Sat": - current_day = 6 - elif ctime_split[0] == "Sun": - current_day = 7 - else: - print('Error with time code') - sys.exit(-1) + my_scheduler = BlockingScheduler() - for i in range(len(user_crontab)): - day_match = False - hour_match = False - - user_crontab[i] = user_crontab.strip() - - contrab_split = str(user_crontab[i]).split(' ') - - if ',' in contrab_split[4]: # if the user wants to be alerted on multiple specific days - contrab_days = contrab_split[4].split(',') - else: - contrab_days = contrab_days - - for element in contrab_days: - if str(current_day) == element or element=='*': - hour_match = True - - - - - if hour_match and day_match: - send_to_user("regular update", pUser_id=user_crontab[i]) + print("update based on cron") + + print(len(user_ids)) #Debug + + for i in range(len(user_ids)): + print("in loop") + cron_split = user_crontab[i].split(" ") + print(cron_split[4], cron_split[1], cron_split[0], cron_split[3], cron_split[2]) + my_scheduler.add_job(update_for_user, 'cron', day_of_week = cron_split[4] , hour= cron_split[1] , minute = cron_split[0], month= cron_split[3] , day=cron_split[2], args=(user_ids[i], )) + + my_scheduler.start() + + print("update based on crontab") - +def update_for_user(p_user_id): + print("updating for {p_user_id}") def send_to_user(pText, pUser_id = 1770205310): diff --git a/telegram_bot/requirements.txt b/telegram_bot/requirements.txt index 146877f..6620615 100644 --- a/telegram_bot/requirements.txt +++ b/telegram_bot/requirements.txt @@ -5,4 +5,5 @@ newsapi-python~=0.2.6 python-dotenv~=0.20.0 requests~=2.27.1 APScheduler~=3.9.1 -cronitor~=1.3.4 +croniter~=1.3.4 +tzlocal==2.1 diff --git a/telegram_bot/testfile.py b/telegram_bot/testfile.py new file mode 100644 index 0000000..4369096 --- /dev/null +++ b/telegram_bot/testfile.py @@ -0,0 +1,41 @@ +from multiprocessing import Process +import time + +class Array_Class: + def __init__(self): + self.array = [] + + def SetArray(self, parray): + array = parray + + def GetArray(self): + return self.array + +my_array = Array_Class() + + +def update_crontab(): + global my_array + array = [] + time.sleep(5) + array.append(1) + my_array.SetArray(array) + print("set done") + +def update_based_on_crontab(): + time.sleep(5) + print(my_array.GetArray()) + + +"""update_crontab() +update_based_on_crontab()""" + +if __name__ == "__main__": + p1 = Process(target= update_crontab, args=()) + p1.start() + p3 = Process(target= update_based_on_crontab, args=() ) + p3.daemon = True + p3.start() + p1.join() + p3.terminate() + p1.terminate() \ No newline at end of file From 689a8cb1fcf98cfc9381b8eba4cddd7b3a095ad7 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Sun, 17 Apr 2022 13:27:25 +0200 Subject: [PATCH 195/263] Removed testfile --- telegram_bot/testfile.py | 41 ---------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 telegram_bot/testfile.py diff --git a/telegram_bot/testfile.py b/telegram_bot/testfile.py deleted file mode 100644 index 4369096..0000000 --- a/telegram_bot/testfile.py +++ /dev/null @@ -1,41 +0,0 @@ -from multiprocessing import Process -import time - -class Array_Class: - def __init__(self): - self.array = [] - - def SetArray(self, parray): - array = parray - - def GetArray(self): - return self.array - -my_array = Array_Class() - - -def update_crontab(): - global my_array - array = [] - time.sleep(5) - array.append(1) - my_array.SetArray(array) - print("set done") - -def update_based_on_crontab(): - time.sleep(5) - print(my_array.GetArray()) - - -"""update_crontab() -update_based_on_crontab()""" - -if __name__ == "__main__": - p1 = Process(target= update_crontab, args=()) - p1.start() - p3 = Process(target= update_based_on_crontab, args=() ) - p3.daemon = True - p3.start() - p1.join() - p3.terminate() - p1.terminate() \ No newline at end of file From 44c57aa881856030e81a6ff4f7cea809a523cef6 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Mon, 18 Apr 2022 17:11:02 +0200 Subject: [PATCH 196/263] Almost working, having some troubles with the blockers still --- telegram_bot/bot_updates.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 7ed1bf2..ff0bf40 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -70,7 +70,7 @@ def update_crontab(pCurrent_Time, p_my_handler): global user_crontab global user_ids - p_my_handler.set_cron_interval(user_id = 1770205310, cron_interval = "50 11 * * *") + p_my_handler.set_cron_interval(user_id = 1770205310, cron_interval = "8 17 * * *") all_users = p_my_handler.get_all_users() @@ -83,16 +83,30 @@ def update_crontab(pCurrent_Time, p_my_handler): user_crontab.append(str(element["cron"])) print(user_ids) - update_based_on_crontab() + update_based_on_crontab(user_ids, user_crontab) + + p1 = Process(target= sleeping_time, args=(pCurrent_Time, )) + p1.start() + p3 = Process(target= update_based_on_crontab, args=(user_ids, user_crontab ) ) + p3.daemon = True + p3.start() + p1.join() + p3.terminate() + p1.terminate() + update_crontab(datetime.datetime.now(), p_my_handler) + + + +def sleeping_time(pCurrent_Time): while True: time_difference = datetime.datetime.now() - pCurrent_Time - if float(str(time_difference).split(':')[0]) >=1: + print(time_difference) + if float(str(time_difference).split(':')[1]) >=1: # every minute (0:00:03.646070) -> example time code update_crontab(datetime.datetime.now()) break - -def update_based_on_crontab(): +def update_based_on_crontab(p_user_ids, p_user_crontab): my_scheduler = BlockingScheduler() @@ -100,15 +114,13 @@ def update_based_on_crontab(): print(len(user_ids)) #Debug - for i in range(len(user_ids)): + for i in range(len(p_user_ids)): print("in loop") - cron_split = user_crontab[i].split(" ") + cron_split = p_user_crontab[i].split(" ") print(cron_split[4], cron_split[1], cron_split[0], cron_split[3], cron_split[2]) - my_scheduler.add_job(update_for_user, 'cron', day_of_week = cron_split[4] , hour= cron_split[1] , minute = cron_split[0], month= cron_split[3] , day=cron_split[2], args=(user_ids[i], )) + my_scheduler.add_job(update_for_user, 'cron', day_of_week = cron_split[4] , hour= cron_split[1] , minute = cron_split[0], month= cron_split[3] , day=cron_split[2], args=(p_user_ids[i], )) my_scheduler.start() - - print("update based on crontab") def update_for_user(p_user_id): print("updating for {p_user_id}") @@ -130,6 +142,7 @@ def send_to_user(pText, pUser_id = 1770205310): if __name__ == "__main__": + print('This script shall not be run directly. Starting main_loop for debugging purposes.') try: main_loop() From 788f63213851ebabdf3373ca6a38114146a0d48c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 17:49:33 +0000 Subject: [PATCH 197/263] Bump @types/node from 17.0.23 to 17.0.25 in /frontend Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 17.0.23 to 17.0.25. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 16 ++++++++-------- frontend/package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e2c0f7f..4dfdd24 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,9 +28,9 @@ "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.2", - "@types/node": "^17.0.23", - "karma": "~6.3.18", + "@types/node": "^17.0.25", "jasmine-core": "~4.1.0", + "karma": "~6.3.18", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.0.0", @@ -2868,9 +2868,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "17.0.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", - "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==", + "version": "17.0.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", + "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==", "dev": true }, "node_modules/@types/parse-json": { @@ -13575,9 +13575,9 @@ "dev": true }, "@types/node": { - "version": "17.0.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", - "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==", + "version": "17.0.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", + "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==", "dev": true }, "@types/parse-json": { diff --git a/frontend/package.json b/frontend/package.json index a1bc013..26e7ac5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,7 +30,7 @@ "@angular/cli": "~13.3.1", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.2", - "@types/node": "^17.0.23", + "@types/node": "^17.0.25", "karma": "~6.3.18", "jasmine-core": "~4.1.0", "karma-chrome-launcher": "~3.1.0", From cd3b3285d117604777991fe4873c158b59e13700 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 17:50:56 +0000 Subject: [PATCH 198/263] Bump @angular/cli from 13.3.1 to 13.3.3 in /frontend Bumps [@angular/cli](https://github.com/angular/angular-cli) from 13.3.1 to 13.3.3. - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/13.3.1...13.3.3) --- updated-dependencies: - dependency-name: "@angular/cli" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 404 +++++++++++++++---------------------- frontend/package.json | 2 +- 2 files changed, 162 insertions(+), 244 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 194d13f..5c4ab35 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,7 +25,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "~13.3.1", - "@angular/cli": "~13.3.1", + "@angular/cli": "~13.3.3", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.2", "@types/node": "^17.0.25", @@ -51,6 +51,39 @@ "node": ">=6.0.0" } }, + "node_modules/@angular-devkit/architect": { + "version": "0.1303.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.3.tgz", + "integrity": "sha512-WRVVBCzLlMqRZVhZXGASHzNJK/OCAvl/DTGhlLuJDIjF7lVGnXHjtwNM8ilYZq949OnC3fly5Z61TfhbN/OHCg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.3", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/architect/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/architect/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/@angular-devkit/build-angular": { "version": "13.3.1", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.1.tgz", @@ -300,28 +333,10 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/@angular-devkit/schematics": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.1.tgz", - "integrity": "sha512-DxXMjlq/sALcHuONZRMTBX5k30XPfN4b6Ue4k7Xl8JKZqyHhEzfXaZzgD9u2cwb7wybKEeF/BZ5eJd8JG525og==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "13.3.1", - "jsonc-parser": "3.0.0", - "magic-string": "0.25.7", - "ora": "5.4.1", - "rxjs": "6.6.7" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", - "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", + "node_modules/@angular-devkit/core": { + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.3.tgz", + "integrity": "sha512-lfQwY9LuVRwcNVzGmyPcwOpb3CAobP4T+c3joR1LLIPS5lzcM0oeCE2bon9N52Ktn4Q/pH98dVtjWL+jSrUADw==", "dev": true, "dependencies": { "ajv": "8.9.0", @@ -345,6 +360,42 @@ } } }, + "node_modules/@angular-devkit/core/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@angular-devkit/core/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@angular-devkit/schematics": { + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.3.tgz", + "integrity": "sha512-S8UNlw6IoR/kxBYbiwesuA7oJGSnFkD6bJwVLhpHdT6Sqrz2/IrjHcNgTJRAvhsOKIbfDtMtXRzl/PUdWEfgyw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.3", + "jsonc-parser": "3.0.0", + "magic-string": "0.25.7", + "ora": "5.4.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, "node_modules/@angular-devkit/schematics/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -400,16 +451,16 @@ "optional": true }, "node_modules/@angular/cli": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.1.tgz", - "integrity": "sha512-0uwU8v3V/2s95X4cZT582J6upReT/ZNw/VAf4p4q51JN+BBvdCEb251xTF+TcOojyToFyJYvg8T28XSrsNsmTQ==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.3.tgz", + "integrity": "sha512-a+nnzFP1FfnypXpAhrHbIBaJcxzegWLZUvVzJQwt6P2z60IoHdvTVmyNbY89qI0LE1SrAokEUO1zW3Yjmu7fUw==", "dev": true, "hasInstallScript": true, "dependencies": { - "@angular-devkit/architect": "0.1303.1", - "@angular-devkit/core": "13.3.1", - "@angular-devkit/schematics": "13.3.1", - "@schematics/angular": "13.3.1", + "@angular-devkit/architect": "0.1303.3", + "@angular-devkit/core": "13.3.3", + "@angular-devkit/schematics": "13.3.3", + "@schematics/angular": "13.3.3", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -435,66 +486,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.1303.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", - "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "13.3.1", - "rxjs": "6.6.7" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", - "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", - "dev": true, - "dependencies": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular/cli/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular/cli/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular/common": { "version": "13.2.6", "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.2.6.tgz", @@ -2657,13 +2648,13 @@ } }, "node_modules/@schematics/angular": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.1.tgz", - "integrity": "sha512-+lrK/d1eJsAK6d6E9TDeg3Vc71bDy1KsE8M+lEINdX9Wax22mAz4pw20X9RSCw5RHgn+XcNUuMsgRJAwVhDNpg==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.3.tgz", + "integrity": "sha512-kX5ghVCmWHcMN+g0pUaFuIJzwrXsVnK4bfid8DckU4EEtfFSv3UA5I1QNJRgpCPxTPhNEAk+3ePN8nzDSjdU+w==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.3.1", - "@angular-devkit/schematics": "13.3.1", + "@angular-devkit/core": "13.3.3", + "@angular-devkit/schematics": "13.3.3", "jsonc-parser": "3.0.0" }, "engines": { @@ -2672,51 +2663,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", - "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", - "dev": true, - "dependencies": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@schematics/angular/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@schematics/angular/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@socket.io/base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", @@ -11569,6 +11515,33 @@ "sourcemap-codec": "1.4.8" } }, + "@angular-devkit/architect": { + "version": "0.1303.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.3.tgz", + "integrity": "sha512-WRVVBCzLlMqRZVhZXGASHzNJK/OCAvl/DTGhlLuJDIjF7lVGnXHjtwNM8ilYZq949OnC3fly5Z61TfhbN/OHCg==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.3", + "rxjs": "6.6.7" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, "@angular-devkit/build-angular": { "version": "13.3.1", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.1.tgz", @@ -11736,33 +11709,50 @@ } } }, - "@angular-devkit/schematics": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.1.tgz", - "integrity": "sha512-DxXMjlq/sALcHuONZRMTBX5k30XPfN4b6Ue4k7Xl8JKZqyHhEzfXaZzgD9u2cwb7wybKEeF/BZ5eJd8JG525og==", + "@angular-devkit/core": { + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.3.tgz", + "integrity": "sha512-lfQwY9LuVRwcNVzGmyPcwOpb3CAobP4T+c3joR1LLIPS5lzcM0oeCE2bon9N52Ktn4Q/pH98dVtjWL+jSrUADw==", "dev": true, "requires": { - "@angular-devkit/core": "13.3.1", + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "@angular-devkit/schematics": { + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.3.tgz", + "integrity": "sha512-S8UNlw6IoR/kxBYbiwesuA7oJGSnFkD6bJwVLhpHdT6Sqrz2/IrjHcNgTJRAvhsOKIbfDtMtXRzl/PUdWEfgyw==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.3", "jsonc-parser": "3.0.0", "magic-string": "0.25.7", "ora": "5.4.1", "rxjs": "6.6.7" }, "dependencies": { - "@angular-devkit/core": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", - "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - } - }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -11806,15 +11796,15 @@ } }, "@angular/cli": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.1.tgz", - "integrity": "sha512-0uwU8v3V/2s95X4cZT582J6upReT/ZNw/VAf4p4q51JN+BBvdCEb251xTF+TcOojyToFyJYvg8T28XSrsNsmTQ==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.3.tgz", + "integrity": "sha512-a+nnzFP1FfnypXpAhrHbIBaJcxzegWLZUvVzJQwt6P2z60IoHdvTVmyNbY89qI0LE1SrAokEUO1zW3Yjmu7fUw==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1303.1", - "@angular-devkit/core": "13.3.1", - "@angular-devkit/schematics": "13.3.1", - "@schematics/angular": "13.3.1", + "@angular-devkit/architect": "0.1303.3", + "@angular-devkit/core": "13.3.3", + "@angular-devkit/schematics": "13.3.3", + "@schematics/angular": "13.3.3", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -11830,47 +11820,6 @@ "semver": "7.3.5", "symbol-observable": "4.0.0", "uuid": "8.3.2" - }, - "dependencies": { - "@angular-devkit/architect": { - "version": "0.1303.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.1.tgz", - "integrity": "sha512-ppaLzNZPrqrI96ddgm1RuEALVpWZsmHbIPLDd0GBwhF6aOkwF0LpZHd5XyS4ktGFZPReiFIjWSVtqV5vaBdRsw==", - "dev": true, - "requires": { - "@angular-devkit/core": "13.3.1", - "rxjs": "6.6.7" - } - }, - "@angular-devkit/core": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", - "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - } - }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } } }, "@angular/common": { @@ -13389,45 +13338,14 @@ "peer": true }, "@schematics/angular": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.1.tgz", - "integrity": "sha512-+lrK/d1eJsAK6d6E9TDeg3Vc71bDy1KsE8M+lEINdX9Wax22mAz4pw20X9RSCw5RHgn+XcNUuMsgRJAwVhDNpg==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.3.tgz", + "integrity": "sha512-kX5ghVCmWHcMN+g0pUaFuIJzwrXsVnK4bfid8DckU4EEtfFSv3UA5I1QNJRgpCPxTPhNEAk+3ePN8nzDSjdU+w==", "dev": true, "requires": { - "@angular-devkit/core": "13.3.1", - "@angular-devkit/schematics": "13.3.1", + "@angular-devkit/core": "13.3.3", + "@angular-devkit/schematics": "13.3.3", "jsonc-parser": "3.0.0" - }, - "dependencies": { - "@angular-devkit/core": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.1.tgz", - "integrity": "sha512-eXAcQaP1mn6rnQb+5bv5NsamY6b34UYM7G+S154Hnma6CTTSGBtcmoNAJs8cekuFqWlw7YgpB/e15jR5OLPkDA==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - } - }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } } }, "@socket.io/base64-arraybuffer": { diff --git a/frontend/package.json b/frontend/package.json index ce9abe3..0c9ad76 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,7 +27,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "~13.3.1", - "@angular/cli": "~13.3.1", + "@angular/cli": "~13.3.3", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.2", "@types/node": "^17.0.25", From 42a2a4e9bd600def828e7bd17c45bf61077104ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 17:52:42 +0000 Subject: [PATCH 199/263] Bump @types/jasmine from 4.0.2 to 4.0.3 in /frontend Bumps [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) from 4.0.2 to 4.0.3. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine) --- updated-dependencies: - dependency-name: "@types/jasmine" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 14 +++++++------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5c4ab35..f47f376 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -27,7 +27,7 @@ "@angular-devkit/build-angular": "~13.3.1", "@angular/cli": "~13.3.3", "@angular/compiler-cli": "~13.2.0", - "@types/jasmine": "~4.0.2", + "@types/jasmine": "~4.0.3", "@types/node": "^17.0.25", "jasmine-core": "~4.1.0", "karma": "~6.3.18", @@ -2796,9 +2796,9 @@ } }, "node_modules/@types/jasmine": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.2.tgz", - "integrity": "sha512-mSPIWhDyQ4nzYdR6Ixy15VhVKMVw93mSUlQxxpVb4S9Hj90lBvg+7kkBw23uYcv8CESPPXit+u3cARYcPeC8Jg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz", + "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", "dev": true }, "node_modules/@types/json-schema": { @@ -13475,9 +13475,9 @@ } }, "@types/jasmine": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.2.tgz", - "integrity": "sha512-mSPIWhDyQ4nzYdR6Ixy15VhVKMVw93mSUlQxxpVb4S9Hj90lBvg+7kkBw23uYcv8CESPPXit+u3cARYcPeC8Jg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz", + "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", "dev": true }, "@types/json-schema": { diff --git a/frontend/package.json b/frontend/package.json index 0c9ad76..efbb8f8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -29,7 +29,7 @@ "@angular-devkit/build-angular": "~13.3.1", "@angular/cli": "~13.3.3", "@angular/compiler-cli": "~13.2.0", - "@types/jasmine": "~4.0.2", + "@types/jasmine": "~4.0.3", "@types/node": "^17.0.25", "karma": "~6.3.18", "jasmine-core": "~4.1.0", From 6db3a7ce32e1c65d5ff73bc8d292c23d115572a6 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Mon, 18 Apr 2022 23:32:19 +0200 Subject: [PATCH 200/263] Update message working, regularity still needs implementation --- telegram_bot/bot_updates.py | 40 ++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index ff0bf40..0121d32 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -7,6 +7,7 @@ __version__ = "0.0.1" __license__ = "None" from calendar import month +from symtable import Symbol from shares.share_fetcher import get_share_price import time import datetime @@ -42,17 +43,9 @@ def main_loop(): current_time_datetime = datetime.datetime.now() my_handler = API_Handler("https://gruppe1.testsites.info/api", "bot@example.com", "bot") - print(time.ctime()) # Debug - """p1 = Process(target= update_crontab, args=(current_time_datetime, my_handler )) - p1.start() - time.sleep(5) - p3 = Process(target= update_based_on_crontab, args=() ) - p3.daemon = True - p3.start() - p1.join() - p3.terminate() - p1.terminate()""" + # update_for_user(5270256395, my_handler) # Debug (running test update for kevins shares) + update_crontab(current_time_datetime, my_handler) @@ -122,8 +115,31 @@ def update_based_on_crontab(p_user_ids, p_user_crontab): my_scheduler.start() -def update_for_user(p_user_id): - print("updating for {p_user_id}") +def update_for_user(p_user_id, p_my_handler): + + share_symbols = [] + share_amounts = [] + share_courses = [] + + my_portfolio = p_my_handler.get_user_portfolio(p_user_id) + + for element in my_portfolio: + if element["count"] != '' and element["symbol"]!= '': + print(element["count"], element["symbol"]) + share_symbols.append(element["symbol"]) + share_amounts.append(element["count"]) + share_courses.append(element["current_price"]) + + my_user = p_my_handler.get_user(p_user_id) + send_to_user("Hello %s this is your share update:"%str(my_user["username"]), pUser_id=p_user_id) + + if len(share_symbols) != 0: + for i in range(len(share_symbols)): + my_update_message = f'Symbol: {share_symbols[i]}\nCurrent Price per Share: {share_courses[i]}\nAmount owned: {share_amounts[i]}\nTotal Investment: {float(share_courses[i]) * float(share_amounts[i])}' + send_to_user(my_update_message, pUser_id=p_user_id) + else: + send_to_user("No shares found for your account. Check https://gruppe1.testsites.info/api to change your settings and add shares.") + def send_to_user(pText, pUser_id = 1770205310): From e9291d3a975021e49ab617c059450e2ce415cdd3 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 19 Apr 2022 08:20:28 +0200 Subject: [PATCH 201/263] Use ISIN numbers instead of share symbols and add comment field to database. Fixes #65 --- api/app/blueprints/portfolio.py | 15 ++++++----- api/app/blueprints/share_price.py | 23 ++++++++-------- api/app/blueprints/shares.py | 43 +++++++++++++++++++----------- api/app/blueprints/transactions.py | 29 ++++++++++++++------ api/app/models.py | 10 ++++--- api/app/schema.py | 23 +++++++++++----- 6 files changed, 90 insertions(+), 53 deletions(-) diff --git a/api/app/blueprints/portfolio.py b/api/app/blueprints/portfolio.py index 2cefe4d..9ead66b 100644 --- a/api/app/blueprints/portfolio.py +++ b/api/app/blueprints/portfolio.py @@ -2,7 +2,7 @@ __author__ = "Florian Kaiser" __copyright__ = "Copyright 2022, Project Aktienbot" __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] __license__ = "GPL 3.0" -__version__ = "1.0.0" +__version__ = "1.0.1" import os @@ -27,22 +27,23 @@ def get_portfolio(): return_portfolio = [] # Get all transactions of current user - transactions = db.session.execute("SELECT symbol, SUM(count), SUM(price), MAX(time) FROM `transactions` WHERE email = '" + email + "' GROUP BY symbol;").all() + transactions = db.session.execute("SELECT isin, comment, SUM(count), SUM(price), MAX(time) FROM `transactions` WHERE email = '" + email + "' GROUP BY isin, comment;").all() # If there are no transactions, return empty portfolio # Otherwise calculate portfolio if transactions is not None: for row in transactions: data = { - "symbol": row[0], - "count": row[1], - # "calculated_price": row[2], - "last_transaction": row[3], + "isin": row[0], + "comment": row[1], + "count": row[2], + # "calculated_price": row[3], + "last_transaction": row[4], 'current_price': 0 } # Add current share value to portfolio - query_share_price = db.session.query(SharePrice).filter_by(symbol=row[0]).order_by(SharePrice.date.desc()).first() + query_share_price = db.session.query(SharePrice).filter_by(isin=row[0]).order_by(SharePrice.date.desc()).first() if query_share_price is not None: data['current_price'] = query_share_price.as_dict()['price'] diff --git a/api/app/blueprints/share_price.py b/api/app/blueprints/share_price.py index d63a844..50f57ae 100644 --- a/api/app/blueprints/share_price.py +++ b/api/app/blueprints/share_price.py @@ -2,17 +2,16 @@ __author__ = "Florian Kaiser" __copyright__ = "Copyright 2022, Project Aktienbot" __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] __license__ = "GPL 3.0" -__version__ = "1.0.0" +__version__ = "1.0.1" import datetime import os from apiflask import APIBlueprint, abort - -from app.models import SharePrice +from app.auth import auth from app.db import database as db from app.helper_functions import make_response -from app.auth import auth +from app.models import SharePrice from app.schema import SymbolPriceSchema share_price_blueprint = APIBlueprint('share_price', __name__, url_prefix='/api') @@ -25,7 +24,7 @@ __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file @share_price_blueprint.doc(summary="Returns all transaction symbols", description="Returns all transaction symbols for all users") def get_transaction_symbols(): # Get all transaction symbols - symbols = db.session.execute("SELECT symbol FROM `transactions` GROUP BY symbol;").all() + symbols = db.session.execute("SELECT isin FROM `transactions` GROUP BY isin;").all() return_symbols = [] for s in symbols: @@ -38,11 +37,11 @@ def get_transaction_symbols(): @share_price_blueprint.output({}, 200) @share_price_blueprint.input(schema=SymbolPriceSchema) @share_price_blueprint.auth_required(auth) -@share_price_blueprint.doc(summary="Returns all transaction symbols", description="Returns all transaction symbols for all users") +@share_price_blueprint.doc(summary="Adds new price for isin", description="Adds new price to database") def add_symbol_price(data): # Check if required data is available - if not check_if_symbol_data_exists(data): - abort(400, message="Symbol missing") + if not check_if_isin_data_exists(data): + abort(400, message="ISIN missing") if not check_if_price_data_exists(data): abort(400, message="Price missing") @@ -52,7 +51,7 @@ def add_symbol_price(data): # Add share price share_price = SharePrice( - symbol=data['symbol'], + isin=data['isin'], price=data['price'], date=datetime.datetime.strptime(data['time'], '%Y-%m-%dT%H:%M:%S.%fZ'), ) @@ -63,11 +62,11 @@ def add_symbol_price(data): return make_response(share_price.as_dict(), 200, "Successfully added price") -def check_if_symbol_data_exists(data): - if 'symbol' not in data: +def check_if_isin_data_exists(data): + if 'isin' not in data: return False - if data['symbol'] == "" or data['symbol'] is None: + if data['isin'] == "" or data['isin'] is None: return False return True diff --git a/api/app/blueprints/shares.py b/api/app/blueprints/shares.py index e015585..ad0ffe2 100644 --- a/api/app/blueprints/shares.py +++ b/api/app/blueprints/shares.py @@ -2,17 +2,16 @@ __author__ = "Florian Kaiser" __copyright__ = "Copyright 2022, Project Aktienbot" __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] __license__ = "GPL 3.0" -__version__ = "1.0.0" +__version__ = "1.0.1" import os from apiflask import APIBlueprint, abort - from app.auth import auth from app.db import database as db from app.helper_functions import make_response, get_email_or_abort_401 from app.models import Share -from app.schema import SymbolSchema, SymbolResponseSchema, DeleteSuccessfulSchema +from app.schema import SymbolSchema, SymbolResponseSchema, DeleteSuccessfulSchema, SymbolRemoveSchema shares_blueprint = APIBlueprint('share', __name__, url_prefix='/api') __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) @@ -27,16 +26,20 @@ def add_symbol(data): email = get_email_or_abort_401() # Check if required data is available - if not check_if_symbol_data_exists(data): - abort(400, message="Symbol missing") + if not check_if_isin_data_exists(data): + abort(400, message="ISIN missing") + + if not check_if_comment_data_exists(data): + abort(400, message="Comment missing") # Check if share already exists - check_share = db.session.query(Share).filter_by(symbol=data['symbol'], email=email).first() + check_share = db.session.query(Share).filter_by(isin=data['isin'], email=email).first() if check_share is None: # Keyword doesn't exist yet for this user -> add it new_symbol = Share( email=email, - symbol=data['symbol'] + isin=data['isin'], + comment=data['comment'] ) db.session.add(new_symbol) db.session.commit() @@ -48,23 +51,23 @@ def add_symbol(data): @shares_blueprint.route('/share', methods=['DELETE']) @shares_blueprint.output(DeleteSuccessfulSchema, 200) -@shares_blueprint.input(schema=SymbolSchema) +@shares_blueprint.input(schema=SymbolRemoveSchema) @shares_blueprint.auth_required(auth) @shares_blueprint.doc(summary="Removes existing symbol", description="Removes existing symbol for current user") def remove_symbol(data): email = get_email_or_abort_401() # Check if required data is available - if not check_if_symbol_data_exists(data): - abort(400, message="Symbol missing") + if not check_if_isin_data_exists(data): + abort(400, message="ISIN missing") # Check if share exists - check_share = db.session.query(Share).filter_by(symbol=data['symbol'], email=email).first() + check_share = db.session.query(Share).filter_by(isin=data['isin'], email=email).first() if check_share is None: abort(500, "Symbol doesn't exist for this user") else: # Delete share - db.session.query(Share).filter_by(symbol=data['symbol'], email=email).delete() + db.session.query(Share).filter_by(isin=data['isin'], email=email).delete() db.session.commit() return make_response({}, 200, "Successfully removed symbol") @@ -89,11 +92,21 @@ def get_symbol(): return make_response(return_symbols, 200, "Successfully loaded symbols") -def check_if_symbol_data_exists(data): - if "symbol" not in data: +def check_if_isin_data_exists(data): + if "isin" not in data: return False - if data['symbol'] == "" or data['symbol'] is None: + if data['isin'] == "" or data['isin'] is None: + return False + + return True + + +def check_if_comment_data_exists(data): + if "comment" not in data: + return False + + if data['comment'] == "" or data['comment'] is None: return False return True diff --git a/api/app/blueprints/transactions.py b/api/app/blueprints/transactions.py index 2bc469b..d34c799 100644 --- a/api/app/blueprints/transactions.py +++ b/api/app/blueprints/transactions.py @@ -2,13 +2,12 @@ __author__ = "Florian Kaiser" __copyright__ = "Copyright 2022, Project Aktienbot" __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] __license__ = "GPL 3.0" -__version__ = "1.0.0" +__version__ = "1.0.1" import datetime import os from apiflask import abort, APIBlueprint - from app.auth import auth from app.db import database as db from app.helper_functions import make_response, get_email_or_abort_401 @@ -28,12 +27,15 @@ def add_transaction(data): email = get_email_or_abort_401() # Check if required data is available - if not check_if_symbol_data_exists(data): - abort(400, "Symbol missing") + if not check_if_isin_data_exists(data): + abort(400, "ISIN missing") if not check_if_time_data_exists(data): abort(400, "Time missing") + if not check_if_comment_data_exists(data): + abort(400, "Comment missing") + if not check_if_count_data_exists(data): abort(400, "Count missing") @@ -43,7 +45,8 @@ def add_transaction(data): # Add transaction new_transaction = Transaction( email=email, - symbol=data['symbol'], + isin=data['isin'], + comment=data['comment'], time=datetime.datetime.strptime(data['time'], '%Y-%m-%dT%H:%M:%S.%fZ'), count=data['count'], price=data['price'] @@ -74,11 +77,11 @@ def get_transaction(): return make_response(return_transactions, 200, "Successfully loaded transactions") -def check_if_symbol_data_exists(data): - if "symbol" not in data: +def check_if_isin_data_exists(data): + if "isin" not in data: return False - if data['symbol'] == "" or data['symbol'] is None: + if data['isin'] == "" or data['isin'] is None: return False return True @@ -94,6 +97,16 @@ def check_if_time_data_exists(data): return True +def check_if_comment_data_exists(data): + if "comment" not in data: + return False + + if data['comment'] == "" or data['comment'] is None: + return False + + return True + + def check_if_count_data_exists(data): if "count" not in data: return False diff --git a/api/app/models.py b/api/app/models.py index 864ce32..8846184 100644 --- a/api/app/models.py +++ b/api/app/models.py @@ -2,7 +2,7 @@ __author__ = "Florian Kaiser" __copyright__ = "Copyright 2022, Project Aktienbot" __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] __license__ = "GPL 3.0" -__version__ = "1.0.0" +__version__ = "1.0.1" from app.db import database as db @@ -30,7 +30,8 @@ class Transaction(db.Model): __tablename__ = 'transactions' t_id = db.Column('t_id', db.Integer(), nullable=False, unique=True, primary_key=True) email = db.Column('email', db.String(255), db.ForeignKey('users.email', ondelete='CASCADE')) - symbol = db.Column('symbol', db.String(255)) + isin = db.Column('isin', db.String(255)) + comment = db.Column('comment', db.String(255)) time = db.Column('time', db.DateTime()) count = db.Column('count', db.Integer()) price = db.Column('price', db.Float()) @@ -53,7 +54,8 @@ class Share(db.Model): __tablename__ = 'shares' a_id = db.Column('a_id', db.Integer(), nullable=False, unique=True, primary_key=True) email = db.Column('email', db.String(255), db.ForeignKey('users.email', ondelete='CASCADE')) - symbol = db.Column('symbol', db.String(255)) + isin = db.Column('isin', db.String(255)) + comment = db.Column('comment', db.String(255)) def as_dict(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} @@ -62,7 +64,7 @@ class Share(db.Model): class SharePrice(db.Model): __tablename__ = 'share_price' id = db.Column('id', db.Integer(), nullable=False, unique=True, primary_key=True) - symbol = db.Column('symbol', db.String(255)) + isin = db.Column('isin', db.String(255)) price = db.Column('price', db.Float()) date = db.Column('date', db.DateTime()) diff --git a/api/app/schema.py b/api/app/schema.py index 134c401..11be0e2 100644 --- a/api/app/schema.py +++ b/api/app/schema.py @@ -2,7 +2,7 @@ __author__ = "Florian Kaiser" __copyright__ = "Copyright 2022, Project Aktienbot" __credits__ = ["Florian Kaiser", "Florian Kellermann", "Linus Eickhof", "Kevin Pauer"] __license__ = "GPL 3.0" -__version__ = "1.0.0" +__version__ = "1.0.1" from apiflask import Schema from apiflask.fields import Integer, String, Boolean, Field, Float @@ -72,18 +72,24 @@ class KeywordSchema(Schema): class SymbolSchema(Schema): - symbol = String() + isin = String() + comment = String() + + +class SymbolRemoveSchema(Schema): + isin = String() class TransactionSchema(Schema): - symbol = String() + isin = String() + comment = String() time = String(validate=validate.Regexp(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z")) count = Integer() price = Float() class SymbolPriceSchema(Schema): - symbol = String() + isin = String() time = String(validate=validate.Regexp(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z")) price = Float() @@ -103,7 +109,8 @@ class KeywordResponseSchema(Schema): class SymbolResponseSchema(Schema): - symbol = String() + isin = String() + comment = String() s_id = Integer() email = Email() @@ -115,14 +122,16 @@ class PortfolioShareResponseSchema(Schema): class TransactionResponseSchema(Schema): email = Email() - symbol = String() + isin = String() + comment = String() time = String() count = Integer() price = Float() class PortfolioResponseSchema(Schema): - symbol = String() + isin = String() + comment = String() last_transaction = String() count = Integer() # price = Float() From 76df6f27becf788f8f304b290378d64c19f9f66e Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 19 Apr 2022 08:30:25 +0200 Subject: [PATCH 202/263] Forgot one user id in the code --- telegram_bot/bot_updates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 0121d32..7c6a4a0 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -138,7 +138,7 @@ def update_for_user(p_user_id, p_my_handler): my_update_message = f'Symbol: {share_symbols[i]}\nCurrent Price per Share: {share_courses[i]}\nAmount owned: {share_amounts[i]}\nTotal Investment: {float(share_courses[i]) * float(share_amounts[i])}' send_to_user(my_update_message, pUser_id=p_user_id) else: - send_to_user("No shares found for your account. Check https://gruppe1.testsites.info/api to change your settings and add shares.") + send_to_user("No shares found for your account. Check https://gruppe1.testsites.info/api to change your settings and add shares.", pUser_id=p_user_id) def send_to_user(pText, pUser_id = 1770205310): From 71fed7fd18c5ab7eade55c67e07c84e8eab51edb Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 19 Apr 2022 21:08:47 +0200 Subject: [PATCH 203/263] added formatting and adapted to isin --- telegram_bot/bot.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index c3ab17e..0f32fe6 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -417,9 +417,12 @@ def send_portfolio(message): bot.send_message(chat_id=user_id, text='You do not have any stocks in your portfolio.') return else: - # tbd: format portfolio - - bot.send_message(chat_id=user_id, text=f'Your portfolio is: _{str(portfolio)}_', parse_mode="MARKDOWN") + for stock in portfolio: + comment = str(stock["comment"]) + count = "{:.2f}".format(float(stock["count"])) + isin = str(stock["isin"]) + worth = "{:.2f}".format(float(stock["current_price"]) * float(stock["count"])) + bot.send_message(chat_id=user_id, text=f'*{comment}*\n_{isin}_\namount: {count}\nworth: ${worth}', parse_mode="MARKDOWN") @bot.message_handler(commands=['newtransaction']) #tbd not working rn @@ -458,7 +461,7 @@ def set_new_transaction_step(message): bot.send_message(chat_id=user_id, text=f'Failed adding transaction. (statuscode {status})') -@bot.message_handler(commands=['interval']) #tbd +@bot.message_handler(commands=['interval']) def send_interval(message): """ send interval for user :type message: message object bot From 7a4a8aaedca5b0e6e0508980a3b1d8dedb72788e Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 19 Apr 2022 21:36:16 +0200 Subject: [PATCH 204/263] fixed case sensitive commands --- telegram_bot/api_handling/api_handler.py | 9 ++-- telegram_bot/bot.py | 55 ++++++++++++------------ 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index c5bc997..49751f0 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -270,22 +270,23 @@ class API_Handler: return self.get_user_transactions(user_id, max_retries-1) - def set_transaction(self, user_id, count, price, symbol, timestamp): + def set_transaction(self, user_id, comment, isin, count, price, time): """sets the transaction of the user Args: user_id (int): id of the user + comment (string): comment of the transaction + isin (string): isin of the transaction count (int): count of the transaction price (float): price of the transaction - symbol (string): symbol of the transaction - timestamp (string): timestamp of the transaction + time (string): time of the transaction Returns: int: status code """ with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - transaction = {"count": count, "price": price, "symbol": symbol, "time": timestamp} + transaction = {"comment": comment, "count": count, "isin": isin, "price": price, "time": time} req = s.post(self.db_adress + "/transaction", json=transaction, headers=headers) return req.status_code diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 0f32fe6..c194789 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -68,7 +68,7 @@ class User: # Currently saving users in this class to test functionality -> late bot = telebot.TeleBot(os.getenv('BOT_API_KEY')) -@bot.message_handler(commands=['start']) # /start -> saving as new user and sending welcome +@bot.message_handler(commands=['start', 'Start']) # /start -> saving as new user and sending welcome def send_start(message): """ Description @@ -90,7 +90,7 @@ def send_start(message): bot.reply_to(message, "Welcome to this share bot project. Type /help to get information on what this bot can do") -@bot.message_handler(commands=['version']) +@bot.message_handler(commands=['version', 'Version']) def send_version(message): """ Sending programm version @@ -104,7 +104,7 @@ def send_version(message): bot.reply_to(message, bot_version) -@bot.message_handler(commands=['help']) # /help -> sending all functions +@bot.message_handler(commands=['help', 'Help']) # /help -> sending all functions def send_welcome(message): """ Send all functions @@ -118,7 +118,7 @@ def send_welcome(message): bot.reply_to(message, "/id or /auth get your user id\n/update get updates on your shares.\n/users see all users.\n/me get my user info\n/news get top article for each keyword.\n/allnews get all news (last 7 days)\n/keywords get all your keywords\n/addkeyword add a keyword\n/removekeyword remove a keyword\n/share get price of specific share\n/portfolio see own portfolio\n/newtransaction add new transaction\n/interval get update interval\n/setinterval set update interval\n_For further details see https://gruppe1.testsites.info _", parse_mode='MARKDOWN') -@bot.message_handler(commands=['users']) +@bot.message_handler(commands=['users', 'Users']) # /users -> sending all users def send_all_users(message): """ Send all users, only possible for admins @@ -141,7 +141,7 @@ def send_all_users(message): bot.send_message(chat_id=user_id, text=answer) -@bot.message_handler(commands=['me']) +@bot.message_handler(commands=['me', 'Me']) # /me -> sending user info def send_user(message): """ Send user data :type message: message object bot @@ -160,7 +160,7 @@ def send_user(message): bot.reply_to(message, 'Your user data:\n' + str(user_data)) -@bot.message_handler(commands=['id', 'auth']) # /auth or /id -> Authentication with user_id over web tool +@bot.message_handler(commands=['id', 'auth', 'Id', 'Auth']) # /auth or /id -> Authentication with user_id over web tool def send_id(message): """ Send user id for authentication with browser @@ -176,7 +176,7 @@ def send_id(message): #function that sends telegram status(running or offline) as message from telegram bot to user -@bot.message_handler(commands=['status']) +@bot.message_handler(commands=['status', 'Status']) def send_status(message): """ Sends status to user @@ -190,7 +190,7 @@ def send_status(message): bot.reply_to(message, "bot is running") -@bot.message_handler(commands=['update']) +@bot.message_handler(commands=['update', 'Update']) # /update -> update shares def send_update(message): """ Send update on shares @@ -228,7 +228,7 @@ def send_update(message): bot.send_message(chat_id=user_id, text=my_update_message) -@bot.message_handler(commands=['share']) +@bot.message_handler(commands=['share', 'Share']) # /share -> get share price def send_share_update(message): """ Send price of a specific share @@ -251,7 +251,7 @@ def send_share_price(message): bot.reply_to(message, str_share_price) -@bot.message_handler(commands=['allnews']) +@bot.message_handler(commands=['allnews', 'Allnews']) # /allnews -> get all news def send_all_news(message): """ Get news for keywords of user @@ -290,7 +290,7 @@ def send_all_news(message): bot.send_message(chat_id=user_id, text='No news found for your keywords.') -@bot.message_handler(commands=['news']) +@bot.message_handler(commands=['news', 'News']) # /news -> get news for specific keyword def send_news(message): """ Get news for keywords of user @@ -326,7 +326,7 @@ def send_news(message): bot.send_message(chat_id=user_id, text=f"_keyword: {keyword}_\n\n" + formatted_article, parse_mode="MARKDOWN") -@bot.message_handler(commands=['addkeyword']) +@bot.message_handler(commands=['addkeyword', 'Addkeyword']) # /addkeyword -> add keyword to user def add_keyword(message): """ Add keyword to user :type message: message object bot @@ -348,10 +348,10 @@ def store_keyword(message): if status == 200: bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" added.') else: - bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" could not be stored. (statuscode {status})') + bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" could not be stored. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info (statuscode {status})') -@bot.message_handler(commands=['removekeyword']) +@bot.message_handler(commands=['removekeyword', 'Removekeyword']) # /removekeyword -> remove keyword from user def remove_keyword(message): """ Remove keyword from user :type message: message object bot @@ -375,7 +375,7 @@ def remove_keyword_step(message): bot.send_message(chat_id=user_id, text=f'Failed deleting keyword "{keyword}". (statuscode {status})') -@bot.message_handler(commands=['keywords']) +@bot.message_handler(commands=['keywords', 'Keywords']) # /keywords -> get keywords of user def send_keywords(message): """ Send keywords of user :type message: message object bot @@ -398,7 +398,7 @@ def send_keywords(message): bot.send_message(chat_id=user_id, text=f'Your keywords are: _{keywords_str}_', parse_mode="MARKDOWN") -@bot.message_handler(commands=['portfolio']) #tbd +@bot.message_handler(commands=['portfolio', 'Portfolio']) #tbd def send_portfolio(message): """ Send portfolio of user :type message: message object bot @@ -425,7 +425,7 @@ def send_portfolio(message): bot.send_message(chat_id=user_id, text=f'*{comment}*\n_{isin}_\namount: {count}\nworth: ${worth}', parse_mode="MARKDOWN") -@bot.message_handler(commands=['newtransaction']) #tbd not working rn +@bot.message_handler(commands=['newtransaction', 'Newtransaction']) #tbd not working rn def set_new_transaction(message): """ Set new transaction for user :type message: message object bot @@ -436,24 +436,25 @@ def set_new_transaction(message): :rtype: none """ user_id = int(message.from_user.id) - bot.send_message(chat_id=user_id, text='Type ",," (time of transaction will be set to now):') + bot.send_message(chat_id=user_id, text='Type ",,," (time of transaction will be set to now):') bot.register_next_step_handler(message, set_new_transaction_step) def set_new_transaction_step(message): user_id = int(message.from_user.id) - if not re.match(r"[A-Za-z0-9]+,[0-9]+(.[0-9]+)?,[0-9]+(.[0-9]+)?", message.text): - bot.send_message(chat_id=user_id, text='Invalid format \n(e.g. AAPL,53.2,120.4).\n Try again with /newtransaction.') + if not re.match(r"[A-Za-z0-9]+,[A-Za-z0-9]+,[0-9]+(.[0-9]+)?,[0-9]+(.[0-9]+)?", message.text): + bot.send_message(chat_id=user_id, text='Invalid format \n(e.g. Apple,US0378331005,53.2,120.4).\n Try again with /newtransaction.') return transaction_data = str(message.text).split(',') - symbol = str(transaction_data[0]) - amount = float(transaction_data[1]) - price = float(transaction_data[2]) - time = dt.datetime.now() + desc = str(transaction_data[0]) + isin = str(transaction_data[1]) + amount = float(transaction_data[2]) + price = float(transaction_data[3]) + time = dt.datetime.now().isoformat() #print("\n\n\n\n\n") #print(f"{symbol},{amount},{price},{time}") - status = api_handler.set_transaction(user_id, amount, price, symbol, time) + status = api_handler.set_transaction(user_id, desc, isin, amount, price, time) if status == 200: bot.send_message(chat_id=user_id, text='Transaction succesfully added.') @@ -461,7 +462,7 @@ def set_new_transaction_step(message): bot.send_message(chat_id=user_id, text=f'Failed adding transaction. (statuscode {status})') -@bot.message_handler(commands=['interval']) +@bot.message_handler(commands=['interval', 'Interval']) #tbd def send_interval(message): """ send interval for user :type message: message object bot @@ -485,7 +486,7 @@ def send_interval(message): bot.send_message(chat_id=user_id, text=f'Your update interval: {interval} (https://crontab.guru/#{formatted_interval})') -@bot.message_handler(commands=['setinterval']) +@bot.message_handler(commands=['setinterval', 'Setinterval']) #tbd def set_new_interval(message): """ Set new interval for user :type message: message object bot From 2b18e026239a0c62fe7480270303c7efea79e0bc Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Wed, 20 Apr 2022 11:03:21 +0200 Subject: [PATCH 205/263] update /update function --- telegram_bot/bot.py | 68 +++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index c194789..c30bfdf 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -190,42 +190,50 @@ def send_status(message): bot.reply_to(message, "bot is running") -@bot.message_handler(commands=['update', 'Update']) # /update -> update shares -def send_update(message): +@bot.message_handler(commands=['update', 'Update']) # /update -> update shares +def update_for_user(message): - """ Send update on shares - :type message: message object bot - :param message: message that was reacted to, in this case always containing '/help' + p_user_id = int(message.from_user.id) + p_my_handler = api_handler + + share_symbols = [] + share_amounts = [] + share_courses = [] + + my_portfolio = p_my_handler.get_user_portfolio(p_user_id) + + for element in my_portfolio: + if element["count"] != '' and element["symbol"]!= '': + print(element["count"], element["symbol"]) + share_symbols.append(element["symbol"]) + share_amounts.append(element["count"]) + share_courses.append(element["current_price"]) + + my_user = p_my_handler.get_user(p_user_id) + send_to_user("Hello %s this is your share update:"%str(my_user["username"]), pUser_id=p_user_id) + + if len(share_symbols) != 0: + for i in range(len(share_symbols)): + my_update_message = f'Symbol: {share_symbols[i]}\nCurrent Price per Share: {share_courses[i]}\nAmount owned: {share_amounts[i]}\nTotal Investment: {float(share_courses[i]) * float(share_amounts[i])}' + send_to_user(my_update_message, pUser_id=p_user_id) + else: + send_to_user("No shares found for your account. Check https://gruppe1.testsites.info to change your settings and add shares.", pUser_id=p_user_id) + + +def send_to_user(pText, pUser_id = 1770205310): + + """ Send message to user + :type pText: string + :param pText: Text to send to user + + :type pUser_id: int + :param pUser_id: user to send to. per default me (Florian Kellermann) :raises: none :rtype: none """ - user_id = int(message.from_user.id) - - #Can be deleted when getting from database - dirname = os.path.dirname(__file__) - json_path = os.path.join(dirname, 'shares/shares_example.json') - - with open(json_path) as json_file: - json_share_data = json.load(json_file) - int_share_count = int(json_share_data['share_count']) - str_username = str(json_share_data['user']) - bot.send_message(chat_id=user_id, text=f'Hello {str_username}. Here is the update on your currently owned shares:') - - - for i in range(int_share_count): - - my_share = json_share_data['shares'][i] - my_share_symbol = str(my_share['symbol']) - my_share_amount = float(my_share['amount_bought']) - my_share_buy_price = float(my_share['price_bought']) - my_share_course = float(share_fetcher.get_share_price(my_share_symbol)) - - - my_update_message = f'Symbol: {my_share_symbol}\nPrice: {my_share_course}\nBought for: {my_share_buy_price}\n\ - Amount owned: {my_share_amount}\nWin/Lose: {(my_share_amount*my_share_course) - (my_share_amount*my_share_buy_price)}' - bot.send_message(chat_id=user_id, text=my_update_message) + bot.send_message(chat_id=pUser_id, text=pText) @bot.message_handler(commands=['share', 'Share']) # /share -> get share price From fa8b511ef4757425e666620576e54fd679aa33de Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Thu, 21 Apr 2022 08:23:48 +0200 Subject: [PATCH 206/263] Scheduler working --- telegram_bot/bot_updates.py | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 7c6a4a0..b0501c4 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -15,6 +15,7 @@ from bot import bot import sys from multiprocessing import Process from apscheduler.schedulers.blocking import BlockingScheduler +from apscheduler.schedulers.background import BackgroundScheduler from api_handling.api_handler import API_Handler @@ -63,7 +64,7 @@ def update_crontab(pCurrent_Time, p_my_handler): global user_crontab global user_ids - p_my_handler.set_cron_interval(user_id = 1770205310, cron_interval = "8 17 * * *") + #p_my_handler.set_cron_interval(user_id = 1770205310, cron_interval = "23 08 * * *") all_users = p_my_handler.get_all_users() @@ -76,44 +77,30 @@ def update_crontab(pCurrent_Time, p_my_handler): user_crontab.append(str(element["cron"])) print(user_ids) - update_based_on_crontab(user_ids, user_crontab) - - p1 = Process(target= sleeping_time, args=(pCurrent_Time, )) - p1.start() - p3 = Process(target= update_based_on_crontab, args=(user_ids, user_crontab ) ) - p3.daemon = True - p3.start() - p1.join() - p3.terminate() - p1.terminate() + + update_based_on_crontab(user_ids, user_crontab, p_my_handler) update_crontab(datetime.datetime.now(), p_my_handler) +def update_based_on_crontab(p_user_ids, p_user_crontab, p_my_handler): -def sleeping_time(pCurrent_Time): - while True: - time_difference = datetime.datetime.now() - pCurrent_Time - print(time_difference) - if float(str(time_difference).split(':')[1]) >=1: # every minute (0:00:03.646070) -> example time code - update_crontab(datetime.datetime.now()) - break - -def update_based_on_crontab(p_user_ids, p_user_crontab): - - my_scheduler = BlockingScheduler() + my_scheduler = BackgroundScheduler() print("update based on cron") print(len(user_ids)) #Debug for i in range(len(p_user_ids)): - print("in loop") cron_split = p_user_crontab[i].split(" ") print(cron_split[4], cron_split[1], cron_split[0], cron_split[3], cron_split[2]) - my_scheduler.add_job(update_for_user, 'cron', day_of_week = cron_split[4] , hour= cron_split[1] , minute = cron_split[0], month= cron_split[3] , day=cron_split[2], args=(p_user_ids[i], )) + my_scheduler.add_job(update_for_user, 'cron', day_of_week = cron_split[4] , hour= cron_split[1] , minute = cron_split[0], month= cron_split[3] , day=cron_split[2], args=(p_user_ids[i], p_my_handler )) my_scheduler.start() + + print("60 second sleep") + time.sleep( 60 ) + my_scheduler.shutdown() def update_for_user(p_user_id, p_my_handler): From cac5883b99d4b0e4689cd631e21668791ca605d0 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Thu, 21 Apr 2022 10:06:08 +0200 Subject: [PATCH 207/263] Better code documentation and removing unnecessary stuff --- telegram_bot/bot_updates.py | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index b0501c4..6269b5e 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -3,7 +3,7 @@ script for regularly sending updates on shares and news based on user interval """ __author__ = "Florian Kellermann, Linus Eickhoff" __date__ = "05.04.2022" -__version__ = "0.0.1" +__version__ = "1.0.1" __license__ = "None" from calendar import month @@ -14,7 +14,6 @@ import datetime from bot import bot import sys from multiprocessing import Process -from apscheduler.schedulers.blocking import BlockingScheduler from apscheduler.schedulers.background import BackgroundScheduler from api_handling.api_handler import API_Handler @@ -85,9 +84,22 @@ def update_crontab(pCurrent_Time, p_my_handler): def update_based_on_crontab(p_user_ids, p_user_crontab, p_my_handler): - my_scheduler = BackgroundScheduler() + """ Check all the crontab codes and add jobs to start in time + :type p_user_ids: array + :param p_user_ids: user id array of all users + + :type p_user_crontab: array + :param p_user_crontab: crontabs for all users equivalent to the user array + + :type p_my_handler: Api_Handler + :param p_my_handler: get database stuff + + :raises: none + + :rtype: none + """ - print("update based on cron") + my_scheduler = BackgroundScheduler() print(len(user_ids)) #Debug @@ -98,12 +110,22 @@ def update_based_on_crontab(p_user_ids, p_user_crontab, p_my_handler): my_scheduler.start() - print("60 second sleep") - time.sleep( 60 ) + time.sleep( 600 ) my_scheduler.shutdown() def update_for_user(p_user_id, p_my_handler): + """ Pull shares and send updates for specific user id + :type p_user_id: integer + :param p_user_id: user id of user that shall receive update + + :type p_my_handler: Api_Handler + :param p_my_handler: handle the api and pull from database + + :raises: none + + :rtype: none + """ share_symbols = [] share_amounts = [] share_courses = [] @@ -146,7 +168,7 @@ def send_to_user(pText, pUser_id = 1770205310): if __name__ == "__main__": - print('This script shall not be run directly. Starting main_loop for debugging purposes.') + print('bot_updates.py starting.') try: main_loop() sys.exit(-1) From 78dfde7a869866d784c2535e3b84fc43e837f8a4 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Mon, 25 Apr 2022 08:05:30 +0200 Subject: [PATCH 208/263] new bot version --- telegram_bot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index c30bfdf..6655ad5 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -34,7 +34,7 @@ from api_handling.api_handler import API_Handler load_dotenv(dotenv_path='.env') -bot_version = "0.2.1" +bot_version = "1.0.1" user_list = [] #create api handler From 112784e702b7bfddd22f7e58afa9da5a3c463eb3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 09:46:24 +0000 Subject: [PATCH 209/263] Update pytelegrambotapi requirement in /telegram_bot Updates the requirements on [pytelegrambotapi](https://github.com/eternnoir/pyTelegramBotAPI) to permit the latest version. - [Release notes](https://github.com/eternnoir/pyTelegramBotAPI/releases) - [Commits](https://github.com/eternnoir/pyTelegramBotAPI/compare/4.4.0...4.5.0) --- updated-dependencies: - dependency-name: pytelegrambotapi dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- telegram_bot/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/requirements.txt b/telegram_bot/requirements.txt index 6b0de09..c0749db 100644 --- a/telegram_bot/requirements.txt +++ b/telegram_bot/requirements.txt @@ -1,4 +1,4 @@ -pyTelegramBotAPI~=4.4.0 +pyTelegramBotAPI~=4.5.0 Markdown~=3.3.6 yfinance~=0.1.70 newsapi-python~=0.2.6 From 7eb702fe1d4ad5cc4a4115dc8cb44acc86232af9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 09:46:27 +0000 Subject: [PATCH 210/263] Update pytest requirement from ~=7.1.1 to ~=7.1.2 in /api Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.1.1...7.1.2) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- api/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/requirements.txt b/api/requirements.txt index 0824c0d..742c783 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -8,7 +8,7 @@ pyjwt==2.3.0 apiflask==0.12.0 flask-cors==3.0.10 bcrypt==3.2.0 -pytest~=7.1.1 +pytest~=7.1.2 pytest-cov marshmallow~=3.15.0 faker~=13.3.4 From 431b730dc2d11f624d8eb9568007ec3cec7b84a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 09:46:31 +0000 Subject: [PATCH 211/263] Update faker requirement from ~=13.3.4 to ~=13.4.0 in /api Updates the requirements on [faker](https://github.com/joke2k/faker) to permit the latest version. - [Release notes](https://github.com/joke2k/faker/releases) - [Changelog](https://github.com/joke2k/faker/blob/master/CHANGELOG.md) - [Commits](https://github.com/joke2k/faker/compare/v13.3.4...v13.4.0) --- updated-dependencies: - dependency-name: faker dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- api/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/requirements.txt b/api/requirements.txt index 0824c0d..548a794 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -11,6 +11,6 @@ bcrypt==3.2.0 pytest~=7.1.1 pytest-cov marshmallow~=3.15.0 -faker~=13.3.4 +faker~=13.4.0 yfinance~=0.1.70 requests~=2.27.1 \ No newline at end of file From 566470ec1270d43e176d0e04d7d36c6168dcd86f Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 25 Apr 2022 17:05:21 +0200 Subject: [PATCH 212/263] Updated Dockerfiles to use second container for bot updates --- .woodpecker/pipeline.yml | 19 ++++++++++++++++++- deploy/aktienbot/docker-compose.yml | 5 +++++ telegram_bot/{Dockerfile => Dockerfile.bot} | 8 +------- telegram_bot/Dockerfile.updates | 14 ++++++++++++++ 4 files changed, 38 insertions(+), 8 deletions(-) rename telegram_bot/{Dockerfile => Dockerfile.bot} (66%) create mode 100644 telegram_bot/Dockerfile.updates diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 7a0d6a1..8551885 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -39,7 +39,24 @@ pipeline: from_secret: password registry: from_secret: registry - dockerfile: telegram_bot/Dockerfile + dockerfile: telegram_bot/Dockerfile.bot + platforms: linux/amd64 + when: + path: "telegram_bot/**" + event: push + + build_bot_updates: + image: woodpeckerci/plugin-docker-buildx + settings: + repo: + from_secret: repo_bot_updates + username: + from_secret: username + password: + from_secret: password + registry: + from_secret: registry + dockerfile: telegram_bot/Dockerfile.updates platforms: linux/amd64 when: path: "telegram_bot/**" diff --git a/deploy/aktienbot/docker-compose.yml b/deploy/aktienbot/docker-compose.yml index 46aee48..991bbdf 100644 --- a/deploy/aktienbot/docker-compose.yml +++ b/deploy/aktienbot/docker-compose.yml @@ -30,6 +30,11 @@ services: env_file: - ${PWD}/.env.bot + aktienbot_bot_updates: + image: registry.flokaiser.com/aktienbot/bot_updates + env_file: + - ${PWD}/.env.bot + mariadb: image: mariadb volumes: diff --git a/telegram_bot/Dockerfile b/telegram_bot/Dockerfile.bot similarity index 66% rename from telegram_bot/Dockerfile rename to telegram_bot/Dockerfile.bot index 49dd01c..0f2e57e 100644 --- a/telegram_bot/Dockerfile +++ b/telegram_bot/Dockerfile.bot @@ -10,11 +10,5 @@ RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-locati # Copy the source code to the working directory COPY telegram_bot /srv/flask_app -# Change file permissions -RUN chmod +x ./deploy/start.sh - -# TODO: Set healthcheck -# HEALTHCHECK --interval=15s --timeout=2s CMD ["./deploy/healthcheck.sh"] - # Run the application -CMD ["./deploy/start.sh"] +CMD ["python bot.py"] diff --git a/telegram_bot/Dockerfile.updates b/telegram_bot/Dockerfile.updates new file mode 100644 index 0000000..66d3898 --- /dev/null +++ b/telegram_bot/Dockerfile.updates @@ -0,0 +1,14 @@ +FROM python:3.10-slim + +# Change the working directory to the root of the project +WORKDIR /srv/flask_app + +# Install the dependencies +COPY telegram_bot/requirements.txt /srv/flask_app/ +RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-location + +# Copy the source code to the working directory +COPY telegram_bot /srv/flask_app + +# Run the application +CMD ["python bot_updates.py"] From b68546b0e778a34702014be0596d1c309f84d8ee Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 25 Apr 2022 17:12:00 +0200 Subject: [PATCH 213/263] Try to fix Dockerfiles --- telegram_bot/Dockerfile.bot | 2 +- telegram_bot/Dockerfile.updates | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/telegram_bot/Dockerfile.bot b/telegram_bot/Dockerfile.bot index 0f2e57e..8b5922f 100644 --- a/telegram_bot/Dockerfile.bot +++ b/telegram_bot/Dockerfile.bot @@ -11,4 +11,4 @@ RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-locati COPY telegram_bot /srv/flask_app # Run the application -CMD ["python bot.py"] +CMD ["/usr/local/bin/python bot.py"] diff --git a/telegram_bot/Dockerfile.updates b/telegram_bot/Dockerfile.updates index 66d3898..37c5a0a 100644 --- a/telegram_bot/Dockerfile.updates +++ b/telegram_bot/Dockerfile.updates @@ -11,4 +11,4 @@ RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-locati COPY telegram_bot /srv/flask_app # Run the application -CMD ["python bot_updates.py"] +CMD ["/usr/local/bin/python bot_updates.py"] From 6d8fb278fec6f98a5f1fe72b4dd927576ad5104a Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Mon, 25 Apr 2022 17:15:07 +0200 Subject: [PATCH 214/263] Try to fix Dockerfiles --- telegram_bot/Dockerfile.bot | 2 +- telegram_bot/Dockerfile.updates | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/telegram_bot/Dockerfile.bot b/telegram_bot/Dockerfile.bot index 8b5922f..58002cc 100644 --- a/telegram_bot/Dockerfile.bot +++ b/telegram_bot/Dockerfile.bot @@ -11,4 +11,4 @@ RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-locati COPY telegram_bot /srv/flask_app # Run the application -CMD ["/usr/local/bin/python bot.py"] +CMD ["/usr/local/bin/python", "bot.py"] diff --git a/telegram_bot/Dockerfile.updates b/telegram_bot/Dockerfile.updates index 37c5a0a..0f7e630 100644 --- a/telegram_bot/Dockerfile.updates +++ b/telegram_bot/Dockerfile.updates @@ -11,4 +11,4 @@ RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-locati COPY telegram_bot /srv/flask_app # Run the application -CMD ["/usr/local/bin/python bot_updates.py"] +CMD ["/usr/local/bin/python", "bot_updates.py"] From d163ba08f3d107761545fc2f4002745fb95302e5 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 25 Apr 2022 17:32:48 +0200 Subject: [PATCH 215/263] implemented News to scheduled User Update --- telegram_bot/bot_updates.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 6269b5e..33da954 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -8,14 +8,18 @@ __license__ = "None" from calendar import month from symtable import Symbol +from dotenv import load_dotenv from shares.share_fetcher import get_share_price +import news.news_fetcher as news_fetcher import time import datetime +import os from bot import bot import sys from multiprocessing import Process from apscheduler.schedulers.background import BackgroundScheduler from api_handling.api_handler import API_Handler +from telegram_bot.news.news_fetcher import format_article ''' @@ -33,7 +37,9 @@ example 0 8 * * * -> daily update at 8am user_ids = [] user_crontab = [] -def main_loop(): +load_dotenv(dotenv_path='.env') + +def main_loop(): #loop? this is not a loop it is executed once (rename?) """ main loop for regularly sending updates :raises: none @@ -41,7 +47,7 @@ def main_loop(): """ current_time_datetime = datetime.datetime.now() - my_handler = API_Handler("https://gruppe1.testsites.info/api", "bot@example.com", "bot") + my_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD"))) # update_for_user(5270256395, my_handler) # Debug (running test update for kevins shares) @@ -140,7 +146,7 @@ def update_for_user(p_user_id, p_my_handler): share_courses.append(element["current_price"]) my_user = p_my_handler.get_user(p_user_id) - send_to_user("Hello %s this is your share update:"%str(my_user["username"]), pUser_id=p_user_id) + send_to_user("Hello %s this is your share update for today:"%str(my_user["username"]), pUser_id=p_user_id) if len(share_symbols) != 0: for i in range(len(share_symbols)): @@ -148,9 +154,19 @@ def update_for_user(p_user_id, p_my_handler): send_to_user(my_update_message, pUser_id=p_user_id) else: send_to_user("No shares found for your account. Check https://gruppe1.testsites.info/api to change your settings and add shares.", pUser_id=p_user_id) + + keywords = p_my_handler.get_user_keywords(p_user_id) + + if(keywords): + send_to_user("If you haven't read yet: \nHere are some interesting news according to your keywords:", pUser_id=p_user_id) + for keyword in keywords: + news = news_fetcher.get_top_news_by_keyword(keyword)["articles"][0] + news_formatted = news_fetcher.format_article(news) + send_to_user(f"_keyword: {keyword}_\n\n{news_formatted}", pUser_id=p_user_id, md_mode=True) + -def send_to_user(pText, pUser_id = 1770205310): +def send_to_user(pText, pUser_id = 1770205310, md_mode = False): """ Send message to user :type pText: string @@ -159,11 +175,18 @@ def send_to_user(pText, pUser_id = 1770205310): :type pUser_id: int :param pUser_id: user to send to. per default me (Florian Kellermann) + :type md_mode: boolean + :param md_mode: if true, parse_mode is markdown + :raises: none :rtype: none """ - bot.send_message(chat_id=pUser_id, text=pText) + if md_mode: + bot.send_message(chat_id=pUser_id, text=pText, parse_mode="MARKDOWN") + else: + bot.send_message(chat_id=pUser_id, text=pText) + if __name__ == "__main__": From 69164c655f64437dc559108314d250176d36ddae Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 25 Apr 2022 17:45:47 +0200 Subject: [PATCH 216/263] added comments --- telegram_bot/news/news_fetcher.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/telegram_bot/news/news_fetcher.py b/telegram_bot/news/news_fetcher.py index 96f50ff..a6dad7e 100644 --- a/telegram_bot/news/news_fetcher.py +++ b/telegram_bot/news/news_fetcher.py @@ -15,12 +15,13 @@ import datetime as dt from newsapi import NewsApiClient from dotenv import load_dotenv -load_dotenv() +load_dotenv() # loads environment vars # Init -api_key = os.getenv('NEWS_API_KEY') -newsapi = NewsApiClient(api_key=api_key) +api_key = os.getenv('NEWS_API_KEY') # get API Key from .env file +newsapi = NewsApiClient(api_key=api_key) # news api from https://newsapi.org/ try: + # get all available news sources (e.g BBC, New York Times, etc.) source_json = requests.get(f"https://newsapi.org/v2/top-headlines/sources?apiKey={api_key}&language=en").json() sources = source_json["sources"] str_sources = ",".join([source["id"] for source in sources]) @@ -38,7 +39,7 @@ def get_all_news_by_keyword(keyword, from_date="2000-01-01"): Returns: JSON/dict: dict containing articles """ - top_headlines = newsapi.get_everything(q=keyword, sources=str_sources, language='en', from_param=from_date) + top_headlines = newsapi.get_everything(q=keyword, sources=str_sources, language='en', from_param=from_date) # keywords can be combined with OR (e.g. keyword = "bitcoin OR ethereum") if(top_headlines["status"] == "ok"): return top_headlines else: @@ -53,7 +54,7 @@ def get_top_news_by_keyword(keyword): Returns: JSON/dict: dict containing articles """ - top_headlines = newsapi.get_top_headlines(q=keyword, sources=str_sources, language='en') + top_headlines = newsapi.get_top_headlines(q=keyword, sources=str_sources, language='en') # get top headlines, measured by popularity from NewsApi if(top_headlines["status"] == "ok"): return top_headlines else: @@ -72,11 +73,11 @@ def format_article(article): sourcename = article["source"]["name"] headline = article["title"] url = article["url"] - formatted_article = f"_{sourcename}_\n*{headline}*\n\n{url}" + formatted_article = f"_{sourcename}_\n*{headline}*\n\n{url}" # formatting in Markdown syntax return formatted_article -if __name__ == '__main__': +if __name__ == '__main__': # only execute if script is called directly -> for simple testing print("this is a module and should not be run directly") print("fetching top news by keyword bitcoin...") @@ -84,4 +85,7 @@ if __name__ == '__main__': articles = get_all_news_by_keyword("bitcoin") formatted_article = format_article(articles["articles"][0]) print(formatted_article) + articles = get_top_news_by_keyword("bitcoin") + formatted_article = format_article(articles["articles"][0]) + print(formatted_article) sys.exit(1) \ No newline at end of file From d91d62e6f0258e6452b0c95ba114415d19f843e0 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 25 Apr 2022 17:48:16 +0200 Subject: [PATCH 217/263] added comments for updater --- telegram_bot/bot_updates.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 33da954..cf36457 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -155,14 +155,14 @@ def update_for_user(p_user_id, p_my_handler): else: send_to_user("No shares found for your account. Check https://gruppe1.testsites.info/api to change your settings and add shares.", pUser_id=p_user_id) - keywords = p_my_handler.get_user_keywords(p_user_id) + keywords = p_my_handler.get_user_keywords(p_user_id) # get keywords as array - if(keywords): + if(keywords): # if keywords exist and array is not empty send_to_user("If you haven't read yet: \nHere are some interesting news according to your keywords:", pUser_id=p_user_id) for keyword in keywords: - news = news_fetcher.get_top_news_by_keyword(keyword)["articles"][0] - news_formatted = news_fetcher.format_article(news) - send_to_user(f"_keyword: {keyword}_\n\n{news_formatted}", pUser_id=p_user_id, md_mode=True) + news = news_fetcher.get_top_news_by_keyword(keyword)["articles"][0] # only use the most popular news + news_formatted = news_fetcher.format_article(news) # format for message + send_to_user(f"_keyword: {keyword}_\n\n{news_formatted}", pUser_id=p_user_id, md_mode=True) # send news with related keyword in Markdown From eab129fce8b843856881f279a0a586cb78a8da09 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 25 Apr 2022 18:03:23 +0200 Subject: [PATCH 218/263] added /me formatting --- telegram_bot/bot.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 6655ad5..fb56f77 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -14,6 +14,7 @@ __license__ = "None" # API Documentation https://core.telegram.org/bots/api # Code examples https://github.com/eternnoir/pyTelegramBotAPI#getting-started +import email import os import telebot @@ -32,16 +33,16 @@ from dotenv import load_dotenv from api_handling.api_handler import API_Handler -load_dotenv(dotenv_path='.env') +load_dotenv(dotenv_path='.env') # load environment variables bot_version = "1.0.1" user_list = [] #create api handler -api_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD"))) +api_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD"))) # get creds from env vars. print("Webserver Token: " + str(api_handler.token)) -class User: # Currently saving users in this class to test functionality -> later database +class User: # Currently saving users in this class to test functionality -> later database REMOVABLE def __init__(self, p_user_id, p_user_name, p_chat_id): """ Initialize a new user @@ -79,7 +80,7 @@ def send_start(message): :rtype: none """ - new_user = User(int(message.from_user.id), message.from_user.first_name, int(message.chat.id)) + new_user = User(int(message.from_user.id), message.from_user.first_name, int(message.chat.id)) # now Database driven CHANGE existing_already = False for known_user in user_list: if known_user.user_id == new_user.user_id: @@ -129,7 +130,6 @@ def send_all_users(message): :rtype: none """ - print('Debug: users command') user_id = int(message.from_user.id) # tbd check if user is admin @@ -152,12 +152,16 @@ def send_user(message): :rtype: none """ user_id = int(message.from_user.id) - user_data = api_handler.get_user(user_id) # tbd: formatting - if not user_data or user_data == None: + user_data = api_handler.get_user(user_id) + if not user_data or user_data == None: # true if user is not registered bot.reply_to(message, "This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info") return - - bot.reply_to(message, 'Your user data:\n' + str(user_data)) + username = user_data['username'] + email = user_data['email'] + user_id = user_data['telegram_user_id'] + cron = user_data['cron'] + admin = user_data['admin'] + bot.reply_to(message, f'Username: {username}\nEmail: {email}\nID: {user_id}\nCron: {cron}\nAdmin: {admin}') # format user data into readable message text @bot.message_handler(commands=['id', 'auth', 'Id', 'Auth']) # /auth or /id -> Authentication with user_id over web tool From 6fae531c1c3e8a4269962988f8978f683d1cd912 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 25 Apr 2022 18:25:22 +0200 Subject: [PATCH 219/263] added comments in bot --- telegram_bot/bot.py | 101 +++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index fb56f77..b700e28 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -179,7 +179,7 @@ def send_id(message): bot.reply_to(message, answer) -#function that sends telegram status(running or offline) as message from telegram bot to user +#function that can be used to ensure that the bot is online and running @bot.message_handler(commands=['status', 'Status']) def send_status(message): @@ -276,28 +276,26 @@ def send_all_news(message): """ user_id = int(message.from_user.id) - keywords = api_handler.get_user_keywords(user_id) + keywords = api_handler.get_user_keywords(user_id) # get keywords of user - if keywords == None: + if keywords == None: # true if user is not registered bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info') return - if not keywords: + if not keywords: # true if user is registered but does not have any keywords bot.send_message(chat_id=user_id, text='You have no keywords. Please add some keywords with /news') return - keywords_search = ' OR '.join(keywords) - print(keywords_search) - now = dt.datetime.now().date() - from_date = now - dt.timedelta(days=7) + keywords_search = ' OR '.join(keywords) # concat all keywords with OR -> NewsAPI can understand OR, AND, NOT etc. + now = dt.datetime.now().date() # get current date + from_date = now - dt.timedelta(days=7) # get date 7 days ago -> limit age of news to 7 days old max from_date_formatted = dt.datetime.strftime(from_date, '%Y-%m-%d') - print(from_date_formatted) - news_list = news.get_all_news_by_keyword(keywords_search, from_date_formatted)["articles"] + news_list = news.get_all_news_by_keyword(keywords_search, from_date_formatted)["articles"] # array of JSON article objects - if news_list: + if news_list: # true if news_list is not empty for article in news_list: formatted_article = news.format_article(article) - bot.send_message(chat_id=user_id, text=formatted_article, parse_mode="MARKDOWN") + bot.send_message(chat_id=user_id, text=formatted_article, parse_mode="MARKDOWN") # Markdown allows to write bold text with * etc. else: bot.send_message(chat_id=user_id, text='No news found for your keywords.') @@ -314,27 +312,27 @@ def send_news(message): :rtype: none """ user_id = int(message.from_user.id) - keywords = api_handler.get_user_keywords(user_id) + keywords = api_handler.get_user_keywords(user_id) # get keywords of user - if keywords == None: + if keywords == None: # true if user is not registered bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info') return - if not keywords: + if not keywords: # true if user is registered but does not have any keywords bot.send_message(chat_id=user_id, text='You have no keywords. Please add some keywords with /addkeyword') return if keywords: for keyword in keywords: top_news = news.get_top_news_by_keyword(keyword)["articles"] - if top_news == None: + if top_news == None: # true if request to NewsAPI failed bot.send_message(chat_id=user_id, text='News Server did not respond correctly. Try again later.') return - if not top_news: + if not top_news: # true if no news found for keyword (empty list) bot.send_message(chat_id=user_id, text=f'No news found for keyword: *{keyword}*', parse_mode="MARKDOWN") return - formatted_article = news.format_article(top_news[0]) + formatted_article = news.format_article(top_news[0]) # only format and send most popular news bot.send_message(chat_id=user_id, text=f"_keyword: {keyword}_\n\n" + formatted_article, parse_mode="MARKDOWN") @@ -350,15 +348,14 @@ def add_keyword(message): """ user_id = int(message.from_user.id) bot.send_message(chat_id=user_id, text='Type keyword to add:') - bot.register_next_step_handler(message, store_keyword) + bot.register_next_step_handler(message, store_keyword) # wait for user to send keyword, then call store_keyword function def store_keyword(message): user_id = int(message.from_user.id) - print(str(user_id)) - keyword = str(message.text).lower() - status = api_handler.set_keyword(user_id, keyword) - if status == 200: - bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" added.') + keyword = str(message.text).lower() # lower to ensure Bitcoin and bitcoin is not stored as individual keywords + status = api_handler.set_keyword(user_id, keyword) # set keyword in database + if status == 200: # statuscode 200 means keyword was added successfully without errors + bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" added.') # duplicate keywords are denied by Database, so no need to check for that here else: bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" could not be stored. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info (statuscode {status})') @@ -375,14 +372,14 @@ def remove_keyword(message): """ user_id = int(message.from_user.id) bot.send_message(chat_id=user_id, text='Type keyword to remove:') - bot.register_next_step_handler(message, remove_keyword_step) + bot.register_next_step_handler(message, remove_keyword_step) # wait for user to send keyword to remove, then call remove_keyword_step function def remove_keyword_step(message): user_id = int(message.from_user.id) keyword = str(message.text).lower() status = api_handler.delete_keyword(user_id, keyword) - if status == 200: - bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" removed.') + if status == 200: # statuscode 200 means keyword was removed successfully without errors + bot.send_message(chat_id=user_id, text=f'Keyword "{keyword}" removed.') # checking if keyword to remove is in database are handled in database, not here else: bot.send_message(chat_id=user_id, text=f'Failed deleting keyword "{keyword}". (statuscode {status})') @@ -398,19 +395,19 @@ def send_keywords(message): :rtype: none """ user_id = int(message.from_user.id) - keywords = api_handler.get_user_keywords(user_id) - if keywords == None: + keywords = api_handler.get_user_keywords(user_id) # get keywords of user + if keywords == None: # true if user is not registered bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info') return - if not keywords: + if not keywords: # true if user is registered but does not have any keywords bot.send_message(chat_id=user_id, text='No keywords set for this account. Add keywords by using /addkeyword') return - else: + else: # send keyword list keywords_str = ', '.join(keywords) bot.send_message(chat_id=user_id, text=f'Your keywords are: _{keywords_str}_', parse_mode="MARKDOWN") -@bot.message_handler(commands=['portfolio', 'Portfolio']) #tbd +@bot.message_handler(commands=['portfolio', 'Portfolio']) def send_portfolio(message): """ Send portfolio of user :type message: message object bot @@ -421,23 +418,23 @@ def send_portfolio(message): :rtype: none """ user_id = int(message.from_user.id) - portfolio = api_handler.get_user_portfolio(user_id) - if portfolio == None: + portfolio = api_handler.get_user_portfolio(user_id) # get portfolio of user as json + if portfolio == None: # true if user is not registered bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info') return - if not portfolio: + if not portfolio: # true if user is registered but does not have any stocks in portfolio bot.send_message(chat_id=user_id, text='You do not have any stocks in your portfolio.') return - else: + else: # send portfolio for stock in portfolio: - comment = str(stock["comment"]) - count = "{:.2f}".format(float(stock["count"])) + comment = str(stock["comment"]) # comment may be written name of stock, comment is made by user when adding an stock to portfolio + count = "{:.2f}".format(float(stock["count"])) # round count to 2 decimal places isin = str(stock["isin"]) - worth = "{:.2f}".format(float(stock["current_price"]) * float(stock["count"])) - bot.send_message(chat_id=user_id, text=f'*{comment}*\n_{isin}_\namount: {count}\nworth: ${worth}', parse_mode="MARKDOWN") + worth = "{:.2f}".format(float(stock["current_price"]) * float(stock["count"])) # round current_price to 2 decimal places + bot.send_message(chat_id=user_id, text=f'*{comment}*\n_{isin}_\namount: {count}\nworth: ${worth}', parse_mode="MARKDOWN") # formatted message in markdown -@bot.message_handler(commands=['newtransaction', 'Newtransaction']) #tbd not working rn +@bot.message_handler(commands=['newtransaction', 'Newtransaction']) #tbd not working rn may be deleted in future def set_new_transaction(message): """ Set new transaction for user :type message: message object bot @@ -474,7 +471,7 @@ def set_new_transaction_step(message): bot.send_message(chat_id=user_id, text=f'Failed adding transaction. (statuscode {status})') -@bot.message_handler(commands=['interval', 'Interval']) #tbd +@bot.message_handler(commands=['interval', 'Interval']) def send_interval(message): """ send interval for user :type message: message object bot @@ -485,20 +482,20 @@ def send_interval(message): :rtype: none """ user_id = int(message.from_user.id) - user_data = api_handler.get_user(user_id) - if user_data == None: + user_data = api_handler.get_user(user_id) # get cron interval of user (stored in user data) + if user_data == None: # true if user is not registered in DB bot.send_message(chat_id=user_id, text='This didn\'t work. Make sure to connect your telegram id (/id) on https://gruppe1.testsites.info and set an interval with /setinterval') return - else: - interval = str(user_data['cron']) - if interval == 'None': + else: # send interval + interval = str(user_data['cron']) # get cron from user data + if interval == 'None': # true if user has no cron set bot.send_message(chat_id=user_id, text='You do not have an interval set. Set one with /setinterval') return - formatted_interval = str(interval).replace(' ', '_') + formatted_interval = str(interval).replace(' ', '_') # replace spaces with underscores to add to url of crontab.guru bot.send_message(chat_id=user_id, text=f'Your update interval: {interval} (https://crontab.guru/#{formatted_interval})') -@bot.message_handler(commands=['setinterval', 'Setinterval']) #tbd +@bot.message_handler(commands=['setinterval', 'Setinterval']) def set_new_interval(message): """ Set new interval for user :type message: message object bot @@ -510,19 +507,19 @@ def set_new_interval(message): """ user_id = int(message.from_user.id) bot.send_message(chat_id=user_id, text='Type interval in cron format:\n(https://crontab.guru/)') - bot.register_next_step_handler(message, set_new_interval_step) + bot.register_next_step_handler(message, set_new_interval_step) # executes function when user sends message def set_new_interval_step(message): user_id = int(message.from_user.id) interval = str(message.text) - status = api_handler.set_cron_interval(user_id, interval) + status = api_handler.set_cron_interval(user_id, interval) # send cron to db if status == 200: bot.send_message(chat_id=user_id, text='Interval succesfully set.') return - if status == -1: # only -1 when interval is invalid + if status == -1: # only -1 when interval is invalid, not a real statuscode, but used from api_handler.set_cron_interval to tell the crontab has the wrong format bot.send_message(chat_id=user_id, text='Invalid interval format. Try again with\n /setinterval.') return else: From 6c99548941fd7926a8f02147229357a2104419ca Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 25 Apr 2022 18:50:29 +0200 Subject: [PATCH 220/263] updated to isin and added comments --- telegram_bot/api_handling/api_handler.py | 57 ++++++++++++------------ 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index 49751f0..f974330 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -12,9 +12,9 @@ __license__ = "None" import sys import os import requests as r -from croniter import croniter - +from croniter import croniter # used for checking cron formatting +# note: for more information about the api visit swagger documentation on https://gruppe1.testsites.info/api/docs#/ class API_Handler: """class for interacting with the api webservice @@ -50,17 +50,17 @@ class API_Handler: """ self.db_adress = db_adress - payload = {'email': email, 'password': password} - with r.Session() as s: - p = s.post(self.db_adress + "/user/login", json=payload) + payload = {'email': email, 'password': password} # credentials for admin account that has all permissions to get and set data (in this case bot account) + with r.Session() as s: # open session + p = s.post(self.db_adress + "/user/login", json=payload) # login to webservice if p.status_code == 200: - self.token = p.json()["data"]['token'] + self.token = p.json()["data"]['token'] # store token for further authentication of requests else: print("Error: " + str(p.status_code) + " invalid credentials") self.token = None - def reauthorize(self, email, password): + def reauthorize(self, email, password): # can be used if token expired """set new credentials Args: @@ -78,7 +78,7 @@ class API_Handler: return None - def get_user(self, user_id, max_retries=10): + def get_user(self, user_id, max_retries=10): # max retries are used recursively if the request fails """gets the shares of the user Args: @@ -92,13 +92,13 @@ class API_Handler: return None with r.Session() as s: - headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} + headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} # authorization is bot_token:user_id (user_id is the id of the user you want to get data from) req = s.get(self.db_adress + "/user", headers=headers) if(req.status_code == 200): return req.json()["data"] else: - return self.get_user(user_id, max_retries-1) + return self.get_user(user_id, max_retries-1) # if request fails try again recursively def get_all_users(self, max_retries=10): @@ -142,10 +142,10 @@ class API_Handler: req = s.get(self.db_adress + "/keywords", headers=headers) if(req.status_code == 200): keywords_json = req.json()["data"] - for keyword in keywords_json: + for keyword in keywords_json: # keywords_json is a list of dictionaries keywords.append(keyword["keyword"]) - return keywords + return keywords # will be empty if no keywords are set else: return self.get_user_keywords(user_id, max_retries-1) @@ -206,7 +206,7 @@ class API_Handler: shares_json = req.json()["data"] shares = [] for share in shares_json: - shares.append(share["symbol"]) + shares.append(share["isin"]) # we only want the isin of the shares return shares @@ -214,23 +214,24 @@ class API_Handler: return self.get_user_shares(user_id, max_retries-1) - def set_share(self, user_id, symbol): + def set_share(self, user_id, isin, comment): """sets the share of the user Args: user_id (int): id of the user - symbol (string): symbol of the share + isin (string): isin of the share + comment (string): comment of the share Returns: int: status code """ with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - req = s.post(self.db_adress + "/share", json={"symbol": symbol}, headers=headers) + req = s.post(self.db_adress + "/share", json={"comment": comment, "isin": isin}, headers=headers) # set share by setting comment and isin, comment can be the real name of the share e.g. "Apple Inc." return req.status_code - def delete_share(self, user_id, symbol): + def delete_share(self, user_id, isin): """deletes the share of the user Args: @@ -242,7 +243,7 @@ class API_Handler: """ with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - req = s.delete(self.db_adress + "/share", json={"symbol": symbol}, headers=headers) + req = s.delete(self.db_adress + "/share", json={"isin": isin}, headers=headers) # to delete a share only the isin is needed because it is unique, shares are not transactions! return req.status_code @@ -277,16 +278,16 @@ class API_Handler: user_id (int): id of the user comment (string): comment of the transaction isin (string): isin of the transaction - count (int): count of the transaction + count (float): count of the transaction price (float): price of the transaction - time (string): time of the transaction + time (string): time of the transaction formatted like e.g. "2011-10-05T14:48:00.000Z" Returns: int: status code """ with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - transaction = {"comment": comment, "count": count, "isin": isin, "price": price, "time": time} + transaction = {"comment": str(comment), "count": float(count), "isin": str(isin), "price": float(price), "time": str(time)} # set transaction as JSON with all the attributes needed according to Swagger docs req = s.post(self.db_adress + "/transaction", json=transaction, headers=headers) return req.status_code @@ -306,9 +307,9 @@ class API_Handler: with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - req = s.get(self.db_adress + "/portfolio", headers=headers) + req = s.get(self.db_adress + "/portfolio", headers=headers) # get portfolio as JSON if req.status_code == 200: - portfolio_dict = req.json()["data"] + portfolio_dict = req.json()["data"] # get the data of the JSON return portfolio_dict else: return self.get_user_portfolio(user_id, max_retries-1) @@ -323,22 +324,22 @@ class API_Handler: Returns: int: status code """ - if not croniter.is_valid(cron_interval): + if not croniter.is_valid(cron_interval): # check if cron_interval is in valid format print("Error: Invalid cron format") - return -1 + return -1 # return error code -1 if invalid cron format with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} - req = s.put(self.db_adress + "/user/setCron", json={"cron": cron_interval}, headers=headers) + req = s.put(self.db_adress + "/user/setCron", json={"cron": str(cron_interval)}, headers=headers) # put not post (see swagger docs) return req.status_code -if __name__ == "__main__": +if __name__ == "__main__": # editable, just for basic on the go testing of new functions print("This is a module for the telegram bot. It is not intended to be run directly.") handler = API_Handler("https://gruppe1.testsites.info/api", "bot@example.com", "bot") print(handler.token) - keywords = handler.get_user_keywords(user_id = 1709356058) #user_id is currently mine (Linus) + keywords = handler.get_user_keywords(user_id = 1709356058) #user_id here is currently mine (Linus) print(keywords) shares = handler.get_user_portfolio(user_id = 1709356058) print("set cron with status: "+ str(handler.set_cron_interval(user_id = 1709356058, cron_interval = "0 0 * * *"))) From 0f4d4e562490b888837a8389e96a60fcde518e6b Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 25 Apr 2022 18:56:19 +0200 Subject: [PATCH 221/263] removed small faults --- telegram_bot/bot.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index b700e28..0dcf793 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -327,13 +327,13 @@ def send_news(message): top_news = news.get_top_news_by_keyword(keyword)["articles"] if top_news == None: # true if request to NewsAPI failed bot.send_message(chat_id=user_id, text='News Server did not respond correctly. Try again later.') - return + if not top_news: # true if no news found for keyword (empty list) bot.send_message(chat_id=user_id, text=f'No news found for keyword: *{keyword}*', parse_mode="MARKDOWN") - return - - formatted_article = news.format_article(top_news[0]) # only format and send most popular news - bot.send_message(chat_id=user_id, text=f"_keyword: {keyword}_\n\n" + formatted_article, parse_mode="MARKDOWN") + + else: + formatted_article = news.format_article(top_news[0]) # only format and send most popular news + bot.send_message(chat_id=user_id, text=f"_keyword: {keyword}_\n\n" + formatted_article, parse_mode="MARKDOWN") @bot.message_handler(commands=['addkeyword', 'Addkeyword']) # /addkeyword -> add keyword to user From 7785fd63688c305f6e0556bc979b63ae99f34053 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 25 Apr 2022 18:59:06 +0200 Subject: [PATCH 222/263] fixed /start --- telegram_bot/bot.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 0dcf793..4a2c877 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -80,14 +80,6 @@ def send_start(message): :rtype: none """ - new_user = User(int(message.from_user.id), message.from_user.first_name, int(message.chat.id)) # now Database driven CHANGE - existing_already = False - for known_user in user_list: - if known_user.user_id == new_user.user_id: - existing_already = True - if existing_already == False: - user_list.append(new_user) - bot.reply_to(message, "Welcome to this share bot project. Type /help to get information on what this bot can do") @@ -330,7 +322,7 @@ def send_news(message): if not top_news: # true if no news found for keyword (empty list) bot.send_message(chat_id=user_id, text=f'No news found for keyword: *{keyword}*', parse_mode="MARKDOWN") - + else: formatted_article = news.format_article(top_news[0]) # only format and send most popular news bot.send_message(chat_id=user_id, text=f"_keyword: {keyword}_\n\n" + formatted_article, parse_mode="MARKDOWN") From 19caebcb362e7bcf8dd39ed4773eafe659f89865 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 25 Apr 2022 19:00:02 +0200 Subject: [PATCH 223/263] small changes --- telegram_bot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 4a2c877..f7c6c6a 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -94,7 +94,7 @@ def send_version(message): :rtype:none """ - bot.reply_to(message, bot_version) + bot.reply_to(message, "the current bot version is " + bot_version) @bot.message_handler(commands=['help', 'Help']) # /help -> sending all functions From 2ed419713b6c7cece5bc00486f83ba55bc19a067 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 25 Apr 2022 19:18:07 +0200 Subject: [PATCH 224/263] updated /users --- telegram_bot/bot.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index f7c6c6a..858e7c6 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -35,8 +35,7 @@ from api_handling.api_handler import API_Handler load_dotenv(dotenv_path='.env') # load environment variables -bot_version = "1.0.1" -user_list = [] +bot_version = "1.0.1" # version of bot #create api handler api_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD"))) # get creds from env vars. @@ -122,15 +121,26 @@ def send_all_users(message): :rtype: none """ - user_id = int(message.from_user.id) + + user_id = int(message.from_user.id) + user_data = api_handler.get_user(user_id) + if(user_data["admin"] == False): # check if user has admin rights + bot.reply_to(message, "You have to be an admin to use this command") + return - # tbd check if user is admin + user_list = api_handler.get_all_users() + user_count = len(user_list) + bot.send_message(chat_id=user_id, text="There are " + str(user_count) + " users in the database:") - answer = 'Current number of users: ' + str(len(user_list)) - bot.send_message(chat_id = user_id, text=answer) - for known_user in user_list: - answer = str(known_user.user_id) + ' : ' + known_user.user_name - bot.send_message(chat_id=user_id, text=answer) + for user in user_list: + + username = user['username'] + email = user['email'] + user_id = user['telegram_user_id'] + cron = user['cron'] + admin = user['admin'] + + bot.send_message(chat_id=user_id, text=f'Username: {username}\nEmail: {email}\nID: {user_id}\nCron: {cron}\nAdmin: {admin}') # format user data into readable message text @bot.message_handler(commands=['me', 'Me']) # /me -> sending user info From 3f2f6a1728453734afdbe2a16bc4f8bd844acd8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 17:34:26 +0000 Subject: [PATCH 225/263] Bump @angular/material from 13.3.3 to 13.3.4 in /frontend Bumps [@angular/material](https://github.com/angular/components) from 13.3.3 to 13.3.4. - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/13.3.3...13.3.4) --- updated-dependencies: - dependency-name: "@angular/material" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 104 +++++-------------------------------- frontend/package.json | 2 +- 2 files changed, 15 insertions(+), 91 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a9bfceb..d6aadcd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,7 +14,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.3.3", + "@angular/material": "^13.3.4", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", @@ -295,51 +295,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/@angular-devkit/core": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.3.tgz", - "integrity": "sha512-lfQwY9LuVRwcNVzGmyPcwOpb3CAobP4T+c3joR1LLIPS5lzcM0oeCE2bon9N52Ktn4Q/pH98dVtjWL+jSrUADw==", - "dev": true, - "dependencies": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/core/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular-devkit/schematics": { "version": "13.3.3", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.3.tgz", @@ -391,9 +346,9 @@ } }, "node_modules/@angular/cdk": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.3.tgz", - "integrity": "sha512-dzd31mta2VwffxbeO4CelMqb7WswLnkC/r2QZXySnc0CTmj44HqXkqdZuEvVgxaKRVpxsYeuBuhhhy8U00YMOw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.4.tgz", + "integrity": "sha512-im4LKxJaIuqFVzmtf650PoiYsn/SZlvBV2zEgzusK8HwQ24C1Lya7NQSApwl8k43h4eKO1OvUKBjqL5uDgEQag==", "dependencies": { "tslib": "^2.3.0" }, @@ -623,15 +578,15 @@ } }, "node_modules/@angular/material": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.3.tgz", - "integrity": "sha512-PkZ7VW3/7tqdBHSOOmq7com7Ir147OEe1+kfgF/G3y8WUutI9jY2cHKARXGWB+5WgcqKr7ol43u239UGkPfFHg==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.4.tgz", + "integrity": "sha512-jK9rWmBaPrE+3re6uIdyvG5DCzc47VUvnP1DqblNnpaa8GCKllb1cFRGqa5GPYB1/96d3wO+RPwzhC06qqV+yw==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "^13.0.0 || ^14.0.0-0", - "@angular/cdk": "13.3.3", + "@angular/cdk": "13.3.4", "@angular/common": "^13.0.0 || ^14.0.0-0", "@angular/core": "^13.0.0 || ^14.0.0-0", "@angular/forms": "^13.0.0 || ^14.0.0-0", @@ -11672,37 +11627,6 @@ } } }, - "@angular-devkit/core": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.3.tgz", - "integrity": "sha512-lfQwY9LuVRwcNVzGmyPcwOpb3CAobP4T+c3joR1LLIPS5lzcM0oeCE2bon9N52Ktn4Q/pH98dVtjWL+jSrUADw==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "dependencies": { - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "@angular-devkit/schematics": { "version": "13.3.3", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.3.tgz", @@ -11742,9 +11666,9 @@ } }, "@angular/cdk": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.3.tgz", - "integrity": "sha512-dzd31mta2VwffxbeO4CelMqb7WswLnkC/r2QZXySnc0CTmj44HqXkqdZuEvVgxaKRVpxsYeuBuhhhy8U00YMOw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.4.tgz", + "integrity": "sha512-im4LKxJaIuqFVzmtf650PoiYsn/SZlvBV2zEgzusK8HwQ24C1Lya7NQSApwl8k43h4eKO1OvUKBjqL5uDgEQag==", "requires": { "parse5": "^5.0.0", "tslib": "^2.3.0" @@ -11904,9 +11828,9 @@ } }, "@angular/material": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.3.tgz", - "integrity": "sha512-PkZ7VW3/7tqdBHSOOmq7com7Ir147OEe1+kfgF/G3y8WUutI9jY2cHKARXGWB+5WgcqKr7ol43u239UGkPfFHg==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.4.tgz", + "integrity": "sha512-jK9rWmBaPrE+3re6uIdyvG5DCzc47VUvnP1DqblNnpaa8GCKllb1cFRGqa5GPYB1/96d3wO+RPwzhC06qqV+yw==", "requires": { "tslib": "^2.3.0" } diff --git a/frontend/package.json b/frontend/package.json index 9cdd286..b4e2695 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.3.3", + "@angular/material": "^13.3.4", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", From 4e17cfb8ab875992f107140e1e3c08966be862e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 17:34:36 +0000 Subject: [PATCH 226/263] Bump tslib from 2.3.1 to 2.4.0 in /frontend Bumps [tslib](https://github.com/Microsoft/tslib) from 2.3.1 to 2.4.0. - [Release notes](https://github.com/Microsoft/tslib/releases) - [Commits](https://github.com/Microsoft/tslib/compare/2.3.1...2.4.0) --- updated-dependencies: - dependency-name: tslib dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 102 +++++++------------------------------ frontend/package.json | 2 +- 2 files changed, 20 insertions(+), 84 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a9bfceb..76d4ca8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -21,7 +21,7 @@ "bootstrap": "^5.1.3", "ngx-cron-editor": "^0.7.3", "rxjs": "~7.5.0", - "tslib": "^2.3.0", + "tslib": "^2.4.0", "zone.js": "~0.11.4" }, "devDependencies": { @@ -213,6 +213,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/@angular-devkit/build-angular/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + }, "node_modules/@angular-devkit/build-webpack": { "version": "0.1303.3", "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.3.tgz", @@ -295,51 +301,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/@angular-devkit/core": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.3.tgz", - "integrity": "sha512-lfQwY9LuVRwcNVzGmyPcwOpb3CAobP4T+c3joR1LLIPS5lzcM0oeCE2bon9N52Ktn4Q/pH98dVtjWL+jSrUADw==", - "dev": true, - "dependencies": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/core/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular-devkit/schematics": { "version": "13.3.3", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.3.tgz", @@ -10785,9 +10746,9 @@ } }, "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "node_modules/type-fest": { "version": "0.21.3", @@ -11611,6 +11572,12 @@ "dev": true } } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true } } }, @@ -11672,37 +11639,6 @@ } } }, - "@angular-devkit/core": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.3.tgz", - "integrity": "sha512-lfQwY9LuVRwcNVzGmyPcwOpb3CAobP4T+c3joR1LLIPS5lzcM0oeCE2bon9N52Ktn4Q/pH98dVtjWL+jSrUADw==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "dependencies": { - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "@angular-devkit/schematics": { "version": "13.3.3", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.3.tgz", @@ -19389,9 +19325,9 @@ "dev": true }, "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "type-fest": { "version": "0.21.3", diff --git a/frontend/package.json b/frontend/package.json index 9cdd286..01bf976 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,7 +23,7 @@ "bootstrap": "^5.1.3", "ngx-cron-editor": "^0.7.3", "rxjs": "~7.5.0", - "tslib": "^2.3.0", + "tslib": "^2.4.0", "zone.js": "~0.11.4" }, "devDependencies": { From 03c02aaabc250d6d28c3c39d08f800e0a725b62d Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 25 Apr 2022 19:56:41 +0200 Subject: [PATCH 227/263] added set admin function --- telegram_bot/api_handling/api_handler.py | 19 +++++++++ telegram_bot/bot.py | 49 ++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index f974330..181fd71 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -37,6 +37,7 @@ class API_Handler: set_portfolio(user_id, portfolio): sets the portfolio of the user delete_portfolio(user_id, portfolio): deletes the portfolio of the user set_cron_interval(user_id, interval): sets the cron interval of the user + set_admin(email, is_admin): sets the admin status of the user with the given email """ @@ -334,6 +335,22 @@ class API_Handler: return req.status_code + def set_admin(self, email, is_admin): + """sets the admin of the user + + Args: + email (string): email of the user + is_admin (String): "true" if user should be Admin, "false" if not + + Returns: + int: status code + """ + with r.Session() as s: + headers = {'Authorization': 'Bearer ' + self.token} # only bot token is needed, user is chosen by email + req = s.put(self.db_adress + "/user/setAdmin", json={"admin": str(is_admin),"email": str(email)}) + return req.status_code + + if __name__ == "__main__": # editable, just for basic on the go testing of new functions print("This is a module for the telegram bot. It is not intended to be run directly.") @@ -346,6 +363,8 @@ if __name__ == "__main__": # editable, just for basic on the go testing of new f user = handler.get_user(user_id = 1709356058) print(user) all_users = handler.get_all_users() + admin_status = handler.set_admin("test@test.com", "true") + print(admin_status) print(all_users) print(shares) sys.exit(1) \ No newline at end of file diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 858e7c6..3bb5b7c 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -97,7 +97,7 @@ def send_version(message): @bot.message_handler(commands=['help', 'Help']) # /help -> sending all functions -def send_welcome(message): +def send_help(message): """ Send all functions :type message: message object bot @@ -133,16 +133,57 @@ def send_all_users(message): bot.send_message(chat_id=user_id, text="There are " + str(user_count) + " users in the database:") for user in user_list: - + username = user['username'] email = user['email'] - user_id = user['telegram_user_id'] + id = user['telegram_user_id'] cron = user['cron'] admin = user['admin'] - bot.send_message(chat_id=user_id, text=f'Username: {username}\nEmail: {email}\nID: {user_id}\nCron: {cron}\nAdmin: {admin}') # format user data into readable message text + bot.send_message(chat_id=user_id, text=f'Username: {username}\nEmail: {email}\nID: {id}\nCron: {cron}\nAdmin: {admin}') # format user data into readable message text +@bot.message_handler(commands=['setAdmin', 'SetAdmin']) # set admin rights to user +def set_admin(message): + + """ Set admin rights to user + :type message: message object bot + :param message: message that was reacted to, in this case always containing '/setAdmin' + + :raises: none + + :rtype: none + """ + user_id = int(message.from_user.id) + user_data = api_handler.get_user(user_id) + + if(user_data["admin"] == False): # check if user has admin rights + bot.reply_to(message, "You have to be an admin to use this command") + return + + bot.send_message(chat_id=user_id, text='send email and "true" if this account should have admin rights, else "false"\n in format: ,') # ask for email and admin rights to set + bot.register_next_step_handler(message, set_admin_step) + +def set_admin_step(message): + str_message = str(message.text) + args_message = str_message.split(',') # split message into email and admin rights + + if len(args_message) != 2: # make sure 2 args (email,is_admin) are given + + bot.reply_to(message, "exactly 2 arguments (,) required, try again") + return + + email = args_message[0] + is_admin = args_message[1] + status = api_handler.set_admin(email, is_admin) # set admin in db + + if(status == 200): + bot.reply_to(message, "Admin rights set") + + else: + bot.reply_to(message, f"Admin rights could not be set ({status})") + + @bot.message_handler(commands=['me', 'Me']) # /me -> sending user info def send_user(message): """ Send user data From b6ab23c0988e863f988ac33a333018854958f6d6 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 25 Apr 2022 19:58:18 +0200 Subject: [PATCH 228/263] updated /help --- telegram_bot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 3bb5b7c..da43b33 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -107,7 +107,7 @@ def send_help(message): :rtype: none """ - bot.reply_to(message, "/id or /auth get your user id\n/update get updates on your shares.\n/users see all users.\n/me get my user info\n/news get top article for each keyword.\n/allnews get all news (last 7 days)\n/keywords get all your keywords\n/addkeyword add a keyword\n/removekeyword remove a keyword\n/share get price of specific share\n/portfolio see own portfolio\n/newtransaction add new transaction\n/interval get update interval\n/setinterval set update interval\n_For further details see https://gruppe1.testsites.info _", parse_mode='MARKDOWN') + bot.reply_to(message, "/id or /auth get your user id\n/update get updates on your shares.\n/setAdmin set admin rights of user (ADMIN)\n/users see all users. (ADMIN)\n/me get my user info\n/news get top article for each keyword.\n/allnews get all news (last 7 days)\n/keywords get all your keywords\n/addkeyword add a keyword\n/removekeyword remove a keyword\n/share get price of specific share\n/portfolio see own portfolio\n/newtransaction add new transaction\n/interval get update interval\n/setinterval set update interval\n_For further details see https://gruppe1.testsites.info _", parse_mode='MARKDOWN') @bot.message_handler(commands=['users', 'Users']) # /users -> sending all users From d6dd5e567cb4558e8f1f579a9adc8f3071666dd5 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Mon, 25 Apr 2022 19:59:03 +0200 Subject: [PATCH 229/263] added comment --- telegram_bot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index da43b33..9e97a09 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -143,7 +143,7 @@ def send_all_users(message): bot.send_message(chat_id=user_id, text=f'Username: {username}\nEmail: {email}\nID: {id}\nCron: {cron}\nAdmin: {admin}') # format user data into readable message text -@bot.message_handler(commands=['setAdmin', 'SetAdmin']) # set admin rights to user +@bot.message_handler(commands=['setAdmin', 'SetAdmin']) # set admin rights to user TBD: not working!! def set_admin(message): """ Set admin rights to user From 16979285451b5e649688f5a34810c6bd2bf22f53 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Mon, 25 Apr 2022 22:39:22 +0200 Subject: [PATCH 230/263] some small changes, just code style and removing unneccesary stuff --- telegram_bot/bot_scheduler.py | 37 ----------------------------------- telegram_bot/bot_updates.py | 12 +++--------- 2 files changed, 3 insertions(+), 46 deletions(-) delete mode 100644 telegram_bot/bot_scheduler.py diff --git a/telegram_bot/bot_scheduler.py b/telegram_bot/bot_scheduler.py deleted file mode 100644 index 8fcef6f..0000000 --- a/telegram_bot/bot_scheduler.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -script for regularly sending updates on shares and news based on user interval -""" -__author__ = "Florian Kellermann, Linus Eickhoff" -__date__ = "05.04.2022" -__version__ = "0.0.1" -__license__ = "None" - -import shares.share_fetcher as share_fetcher -import news.news_fetcher as news_fetcher - -import datetime -import sys -from apscheduler.schedulers.blocking import BlockingScheduler - -''' -* * * * * code -┬ ┬ ┬ ┬ ┬ -│ │ │ │ │ -│ │ │ │ └──── weekday (0->Monday, 7->Sunday) -│ │ │ └────── Month (1-12) -│ │ └──────── Day (1-31) -│ └────────── Hour (0-23) -└──────────── Minute (0-59) - -example 0 8 * * * -> daily update at 8am -''' - -def user_updates(): - """sends timed updates automatically to user - - Args: - - Returns: - - """ - return \ No newline at end of file diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index cf36457..c6dce42 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -39,7 +39,7 @@ user_crontab = [] load_dotenv(dotenv_path='.env') -def main_loop(): #loop? this is not a loop it is executed once (rename?) +def start_updater(): """ main loop for regularly sending updates :raises: none @@ -49,10 +49,6 @@ def main_loop(): #loop? this is not a loop it is executed once (rename?) current_time_datetime = datetime.datetime.now() my_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD"))) - - # update_for_user(5270256395, my_handler) # Debug (running test update for kevins shares) - - update_crontab(current_time_datetime, my_handler) @@ -69,8 +65,6 @@ def update_crontab(pCurrent_Time, p_my_handler): global user_crontab global user_ids - #p_my_handler.set_cron_interval(user_id = 1770205310, cron_interval = "23 08 * * *") - all_users = p_my_handler.get_all_users() user_ids = [] @@ -166,7 +160,7 @@ def update_for_user(p_user_id, p_my_handler): -def send_to_user(pText, pUser_id = 1770205310, md_mode = False): +def send_to_user(pText, pUser_id , md_mode = False): """ Send message to user :type pText: string @@ -193,7 +187,7 @@ if __name__ == "__main__": print('bot_updates.py starting.') try: - main_loop() + start_updater() sys.exit(-1) except KeyboardInterrupt: print("Ending") From 05bd1948f3e200f028e1e46b2ecc6e288bc9b8a0 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 26 Apr 2022 07:39:06 +0200 Subject: [PATCH 231/263] removed some stuff --- telegram_bot/bot_updates.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index c6dce42..1816581 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -40,19 +40,18 @@ user_crontab = [] load_dotenv(dotenv_path='.env') def start_updater(): - """ main loop for regularly sending updates + """ starting function for regularly sending updates :raises: none :rtype: none """ - current_time_datetime = datetime.datetime.now() my_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD"))) - update_crontab(current_time_datetime, my_handler) + update_crontab(my_handler) -def update_crontab(pCurrent_Time, p_my_handler): +def update_crontab(p_my_handler): """ Updating crontab lists every hour :type pCurrent_Time: time when starting crontab update :param pCurrent_Time: datetime @@ -79,7 +78,7 @@ def update_crontab(pCurrent_Time, p_my_handler): update_based_on_crontab(user_ids, user_crontab, p_my_handler) - update_crontab(datetime.datetime.now(), p_my_handler) + update_crontab(p_my_handler) def update_based_on_crontab(p_user_ids, p_user_crontab, p_my_handler): @@ -101,8 +100,6 @@ def update_based_on_crontab(p_user_ids, p_user_crontab, p_my_handler): my_scheduler = BackgroundScheduler() - print(len(user_ids)) #Debug - for i in range(len(p_user_ids)): cron_split = p_user_crontab[i].split(" ") print(cron_split[4], cron_split[1], cron_split[0], cron_split[3], cron_split[2]) @@ -184,8 +181,6 @@ def send_to_user(pText, pUser_id , md_mode = False): if __name__ == "__main__": - - print('bot_updates.py starting.') try: start_updater() sys.exit(-1) From 3ebe658b6889a5666e92497424584191fa2404ae Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 26 Apr 2022 08:33:48 +0200 Subject: [PATCH 232/263] removed some more stuff --- telegram_bot/bot_updates.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 1816581..2638dec 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -19,7 +19,7 @@ import sys from multiprocessing import Process from apscheduler.schedulers.background import BackgroundScheduler from api_handling.api_handler import API_Handler -from telegram_bot.news.news_fetcher import format_article +from news.news_fetcher import format_article ''' @@ -74,8 +74,6 @@ def update_crontab(p_my_handler): user_ids.append(int(element["telegram_user_id"])) user_crontab.append(str(element["cron"])) - print(user_ids) - update_based_on_crontab(user_ids, user_crontab, p_my_handler) update_crontab(p_my_handler) @@ -102,7 +100,6 @@ def update_based_on_crontab(p_user_ids, p_user_crontab, p_my_handler): for i in range(len(p_user_ids)): cron_split = p_user_crontab[i].split(" ") - print(cron_split[4], cron_split[1], cron_split[0], cron_split[3], cron_split[2]) my_scheduler.add_job(update_for_user, 'cron', day_of_week = cron_split[4] , hour= cron_split[1] , minute = cron_split[0], month= cron_split[3] , day=cron_split[2], args=(p_user_ids[i], p_my_handler )) my_scheduler.start() From 8678f679c46ce93e66c869d22310b81483581198 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 26 Apr 2022 08:43:51 +0200 Subject: [PATCH 233/263] Forgot to delete one line --- telegram_bot/bot_updates.py | 1 + 1 file changed, 1 insertion(+) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 2638dec..4fa5459 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -47,6 +47,7 @@ def start_updater(): """ my_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD"))) + update_crontab(my_handler) From 585d290355e8dba66c7933770318c5d058ce9707 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 26 Apr 2022 09:02:21 +0200 Subject: [PATCH 234/263] Some prints --- telegram_bot/bot_updates.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 4fa5459..47cefa7 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -46,6 +46,8 @@ def start_updater(): :rtype: none """ + print("Bot updates started") + my_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD"))) @@ -75,6 +77,8 @@ def update_crontab(p_my_handler): user_ids.append(int(element["telegram_user_id"])) user_crontab.append(str(element["cron"])) + print(user_ids) + update_based_on_crontab(user_ids, user_crontab, p_my_handler) update_crontab(p_my_handler) @@ -101,6 +105,7 @@ def update_based_on_crontab(p_user_ids, p_user_crontab, p_my_handler): for i in range(len(p_user_ids)): cron_split = p_user_crontab[i].split(" ") + print(cron_split[4], cron_split[1], cron_split[0], cron_split[3], cron_split[2]) my_scheduler.add_job(update_for_user, 'cron', day_of_week = cron_split[4] , hour= cron_split[1] , minute = cron_split[0], month= cron_split[3] , day=cron_split[2], args=(p_user_ids[i], p_my_handler )) my_scheduler.start() From ea5c01bd04988d94f437281d145796c759f68b71 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 26 Apr 2022 09:12:21 +0200 Subject: [PATCH 235/263] Update Dockerfile and pipeline --- .woodpecker/pipeline.yml | 19 +------------------ telegram_bot/{Dockerfile.bot => Dockerfile} | 2 +- telegram_bot/Dockerfile.updates | 14 -------------- 3 files changed, 2 insertions(+), 33 deletions(-) rename telegram_bot/{Dockerfile.bot => Dockerfile} (84%) delete mode 100644 telegram_bot/Dockerfile.updates diff --git a/.woodpecker/pipeline.yml b/.woodpecker/pipeline.yml index 8551885..7a0d6a1 100644 --- a/.woodpecker/pipeline.yml +++ b/.woodpecker/pipeline.yml @@ -39,24 +39,7 @@ pipeline: from_secret: password registry: from_secret: registry - dockerfile: telegram_bot/Dockerfile.bot - platforms: linux/amd64 - when: - path: "telegram_bot/**" - event: push - - build_bot_updates: - image: woodpeckerci/plugin-docker-buildx - settings: - repo: - from_secret: repo_bot_updates - username: - from_secret: username - password: - from_secret: password - registry: - from_secret: registry - dockerfile: telegram_bot/Dockerfile.updates + dockerfile: telegram_bot/Dockerfile platforms: linux/amd64 when: path: "telegram_bot/**" diff --git a/telegram_bot/Dockerfile.bot b/telegram_bot/Dockerfile similarity index 84% rename from telegram_bot/Dockerfile.bot rename to telegram_bot/Dockerfile index 58002cc..de4bda8 100644 --- a/telegram_bot/Dockerfile.bot +++ b/telegram_bot/Dockerfile @@ -11,4 +11,4 @@ RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-locati COPY telegram_bot /srv/flask_app # Run the application -CMD ["/usr/local/bin/python", "bot.py"] +ENTRYPOINT ["/bin/sh" "-c" "python bot.py && python bot_updates.py"] diff --git a/telegram_bot/Dockerfile.updates b/telegram_bot/Dockerfile.updates deleted file mode 100644 index 0f7e630..0000000 --- a/telegram_bot/Dockerfile.updates +++ /dev/null @@ -1,14 +0,0 @@ -FROM python:3.10-slim - -# Change the working directory to the root of the project -WORKDIR /srv/flask_app - -# Install the dependencies -COPY telegram_bot/requirements.txt /srv/flask_app/ -RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-location - -# Copy the source code to the working directory -COPY telegram_bot /srv/flask_app - -# Run the application -CMD ["/usr/local/bin/python", "bot_updates.py"] From 8f60bc5f160f1f0f0edf076a15dd7a4423a81c19 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 26 Apr 2022 09:15:10 +0200 Subject: [PATCH 236/263] Update Dockerfile --- telegram_bot/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/Dockerfile b/telegram_bot/Dockerfile index de4bda8..e5c4106 100644 --- a/telegram_bot/Dockerfile +++ b/telegram_bot/Dockerfile @@ -11,4 +11,4 @@ RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-locati COPY telegram_bot /srv/flask_app # Run the application -ENTRYPOINT ["/bin/sh" "-c" "python bot.py && python bot_updates.py"] +ENTRYPOINT ["/bin/sh", "-c", "python bot.py && python bot_updates.py"] From 1786cf8df0dfdcadde99f9e8ed4146e5a75d0d48 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 26 Apr 2022 09:19:46 +0200 Subject: [PATCH 237/263] Update Dockerfile and docker-compose --- deploy/aktienbot/.env.bot | 3 +++ deploy/aktienbot/docker-compose.yml | 5 ----- telegram_bot/Dockerfile | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/deploy/aktienbot/.env.bot b/deploy/aktienbot/.env.bot index c64cd3c..8b67787 100644 --- a/deploy/aktienbot/.env.bot +++ b/deploy/aktienbot/.env.bot @@ -1,3 +1,6 @@ BOT_API_KEY= NEWS_API_KEY= SECRET_KEY= + +BOT_EMAIL= +BOT_PASSWORD= \ No newline at end of file diff --git a/deploy/aktienbot/docker-compose.yml b/deploy/aktienbot/docker-compose.yml index 991bbdf..46aee48 100644 --- a/deploy/aktienbot/docker-compose.yml +++ b/deploy/aktienbot/docker-compose.yml @@ -30,11 +30,6 @@ services: env_file: - ${PWD}/.env.bot - aktienbot_bot_updates: - image: registry.flokaiser.com/aktienbot/bot_updates - env_file: - - ${PWD}/.env.bot - mariadb: image: mariadb volumes: diff --git a/telegram_bot/Dockerfile b/telegram_bot/Dockerfile index e5c4106..c2fc640 100644 --- a/telegram_bot/Dockerfile +++ b/telegram_bot/Dockerfile @@ -11,4 +11,4 @@ RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-locati COPY telegram_bot /srv/flask_app # Run the application -ENTRYPOINT ["/bin/sh", "-c", "python bot.py && python bot_updates.py"] +ENTRYPOINT ["/bin/sh", "-c", "'python bot.py && python bot_updates.py'"] From eac839f0bdb02df3c43a1414a79d069663f264b5 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 26 Apr 2022 09:31:25 +0200 Subject: [PATCH 238/263] Removed default value in bot --- telegram_bot/bot.py | 2 +- telegram_bot/bot_updates.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 9e97a09..684f96c 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -267,7 +267,7 @@ def update_for_user(message): send_to_user("No shares found for your account. Check https://gruppe1.testsites.info to change your settings and add shares.", pUser_id=p_user_id) -def send_to_user(pText, pUser_id = 1770205310): +def send_to_user(pText, pUser_id): """ Send message to user :type pText: string diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 47cefa7..6d81fcd 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -19,7 +19,6 @@ import sys from multiprocessing import Process from apscheduler.schedulers.background import BackgroundScheduler from api_handling.api_handler import API_Handler -from news.news_fetcher import format_article ''' From 9e344c4f9ab003c57a8b6c8bad7178605b7f3ac1 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 26 Apr 2022 09:36:25 +0200 Subject: [PATCH 239/263] update to isin --- telegram_bot/bot.py | 6 +++--- telegram_bot/bot_updates.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 684f96c..a2cc2a6 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -250,9 +250,9 @@ def update_for_user(message): my_portfolio = p_my_handler.get_user_portfolio(p_user_id) for element in my_portfolio: - if element["count"] != '' and element["symbol"]!= '': - print(element["count"], element["symbol"]) - share_symbols.append(element["symbol"]) + if element["count"] != '' and element["isin"]!= '': + print(element["count"], element["isin"]) + share_symbols.append(element["isin"]) share_amounts.append(element["count"]) share_courses.append(element["current_price"]) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 6d81fcd..46df917 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -132,9 +132,9 @@ def update_for_user(p_user_id, p_my_handler): my_portfolio = p_my_handler.get_user_portfolio(p_user_id) for element in my_portfolio: - if element["count"] != '' and element["symbol"]!= '': - print(element["count"], element["symbol"]) - share_symbols.append(element["symbol"]) + if element["count"] != '' and element["isin"]!= '': + print(element["count"], element["isin"]) + share_symbols.append(element["isin"]) share_amounts.append(element["count"]) share_courses.append(element["current_price"]) From b4165ada6f501e474f9ea5f9c7ef742fcd3ec79e Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 26 Apr 2022 09:46:13 +0200 Subject: [PATCH 240/263] Update Dockerfile --- telegram_bot/Dockerfile | 4 ++-- telegram_bot/deploy/start.sh | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/telegram_bot/Dockerfile b/telegram_bot/Dockerfile index c2fc640..00e7604 100644 --- a/telegram_bot/Dockerfile +++ b/telegram_bot/Dockerfile @@ -10,5 +10,5 @@ RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-locati # Copy the source code to the working directory COPY telegram_bot /srv/flask_app -# Run the application -ENTRYPOINT ["/bin/sh", "-c", "'python bot.py && python bot_updates.py'"] +# Run the app +CMD ["./deploy/start.sh"] \ No newline at end of file diff --git a/telegram_bot/deploy/start.sh b/telegram_bot/deploy/start.sh index 232326f..75c6e21 100644 --- a/telegram_bot/deploy/start.sh +++ b/telegram_bot/deploy/start.sh @@ -1,3 +1,4 @@ #!/usr/bin/env sh -python bot.py \ No newline at end of file +python bot.py & +python bot_updates.py \ No newline at end of file From 1cbe104fc235107301f81e1a77abb836c6a12f7b Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 26 Apr 2022 09:48:34 +0200 Subject: [PATCH 241/263] Update Dockerfile #2 --- telegram_bot/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/telegram_bot/Dockerfile b/telegram_bot/Dockerfile index 00e7604..ca7ffb7 100644 --- a/telegram_bot/Dockerfile +++ b/telegram_bot/Dockerfile @@ -10,5 +10,8 @@ RUN pip install -r requirements.txt --src /usr/local/src --no-warn-script-locati # Copy the source code to the working directory COPY telegram_bot /srv/flask_app +# Change file permissions +RUN chmod +x ./deploy/start.sh + # Run the app CMD ["./deploy/start.sh"] \ No newline at end of file From 275444fb958979f58414c73170629083e9b8de1e Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 26 Apr 2022 10:34:17 +0200 Subject: [PATCH 242/263] Removed user class --- telegram_bot/bot.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index a2cc2a6..f5c3bef 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -41,31 +41,6 @@ bot_version = "1.0.1" # version of bot api_handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD"))) # get creds from env vars. print("Webserver Token: " + str(api_handler.token)) -class User: # Currently saving users in this class to test functionality -> later database REMOVABLE - def __init__(self, p_user_id, p_user_name, p_chat_id): - - """ Initialize a new user - :type self: User - :param self: object of the class - - :type p_user_id: int - :param p_user_id: telegram user id - - :type p_user_name: str - :param p_user_name: first name of user - - :type p_chat_id: int - :param p_chat_id: telegram chat id - - :raises: - - :rtype: - """ - - self.user_id = int(p_user_id) - self.chat_id = int(p_chat_id) - self.user_name = str(p_user_name) - bot = telebot.TeleBot(os.getenv('BOT_API_KEY')) @bot.message_handler(commands=['start', 'Start']) # /start -> saving as new user and sending welcome From f7b678e56d8a5f871f3495ff0446fcb62090f1d4 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 26 Apr 2022 11:23:18 +0200 Subject: [PATCH 243/263] Update api docs --- documentation/api/openapi.json | 1405 +++++++++++++++++++++++++++++++- documentation/api/postman.json | 1317 ++++++++++++++++++++---------- 2 files changed, 2295 insertions(+), 427 deletions(-) diff --git a/documentation/api/openapi.json b/documentation/api/openapi.json index f16137b..6b0c300 100644 --- a/documentation/api/openapi.json +++ b/documentation/api/openapi.json @@ -1 +1,1404 @@ -{"components":{"schemas":{"AdminData":{"properties":{"admin":{"type":"boolean"},"email":{"format":"email","type":"string"}},"type":"object"},"DeleteSuccessful":{"properties":{},"type":"object"},"DeleteUser":{"properties":{"email":{"format":"email","type":"string"}},"type":"object"},"HTTPError":{"properties":{"detail":{"type":"object"},"message":{"type":"string"}},"type":"object"},"Keyword":{"properties":{"keyword":{"type":"string"}},"type":"object"},"KeywordResponse":{"properties":{"email":{"format":"email","type":"string"},"keyword":{"type":"string"},"s_id":{"type":"integer"}},"type":"object"},"LoginData":{"properties":{"email":{"format":"email","type":"string"},"password":{"type":"string"}},"type":"object"},"PortfolioResponse":{"properties":{"count":{"type":"integer"},"last_transaction":{"type":"string"},"symbol":{"type":"string"}},"type":"object"},"RegisterData":{"properties":{"email":{"format":"email","type":"string"},"password":{"type":"string"},"username":{"type":"string"}},"type":"object"},"Symbol":{"properties":{"symbol":{"type":"string"}},"type":"object"},"SymbolResponse":{"properties":{"email":{"format":"email","type":"string"},"s_id":{"type":"integer"},"symbol":{"type":"string"}},"type":"object"},"TelegramId":{"properties":{"telegram_user_id":{"type":"string"}},"type":"object"},"Token":{"properties":{"token":{"type":"string"}},"type":"object"},"Transaction":{"properties":{"count":{"type":"integer"},"price":{"type":"number"},"symbol":{"type":"string"},"time":{"pattern":"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z","type":"string"}},"type":"object"},"TransactionResponse":{"properties":{"count":{"type":"integer"},"email":{"format":"email","type":"string"},"price":{"type":"number"},"symbol":{"type":"string"},"time":{"type":"string"}},"type":"object"},"UpdateUserData":{"properties":{"password":{"type":"string"},"username":{"type":"string"}},"type":"object"},"Users":{"properties":{"admin":{"type":"boolean"},"email":{"format":"email","type":"string"},"password":{"type":"string"},"telegram_user_id":{"type":"string"},"username":{"type":"string"}},"type":"object"},"ValidationError":{"properties":{"detail":{"properties":{"":{"properties":{"":{"items":{"type":"string"},"type":"array"}},"type":"object"}},"type":"object"},"message":{"type":"string"}},"type":"object"}},"securitySchemes":{"BearerAuth":{"scheme":"Bearer","type":"http"}}},"info":{"description":"Webengineering 2 | Telegram Aktienbot","title":"APIFlask","version":"0.0.1"},"openapi":"3.0.3","paths":{"/api/keyword":{"delete":{"description":"Removes existing keyword for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Keyword"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/DeleteSuccessful"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Removes existing keyword","tags":["Keyword"]},"post":{"description":"Adds new keyword for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Keyword"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/KeywordResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Add new keyword","tags":["Keyword"]}},"/api/keywords":{"get":{"description":"Returns all keywords for current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/KeywordResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns all keywords","tags":["Keyword"]}},"/api/portfolio":{"get":{"description":"Returns all shares of current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/PortfolioResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns portfolio","tags":["Portfolio"]}},"/api/share":{"delete":{"description":"Removes existing symbol for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Symbol"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/DeleteSuccessful"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Removes existing symbol","tags":["Share"]},"post":{"description":"Adds new symbol for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Symbol"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/SymbolResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Add new symbol","tags":["Share"]}},"/api/shares":{"get":{"description":"Returns all symbols for current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/SymbolResponse"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns all symbols","tags":["Share"]}},"/api/telegram":{"post":{"description":"Connects telegram user id to user account","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelegramId"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Users"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Connects telegram user id","tags":["Telegram"]}},"/api/transaction":{"post":{"description":"Adds new transaction for current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transaction"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/TransactionResponse"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Adds new transaction","tags":["Transaction"]}},"/api/transactions":{"get":{"description":"Returns all transactions for current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Transaction"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Returns all transactions","tags":["Transaction"]}},"/api/user":{"delete":{"description":"Deletes user by username","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteUser"}}}},"responses":{"204":{"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Delete user","tags":["Users"]},"get":{"description":"Returns current user","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Users"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Get current user","tags":["Users"]},"put":{"description":"Changes password and/or username of current user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserData"}}}},"responses":{"204":{"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Update user","tags":["Users"]}},"/api/user/login":{"post":{"description":"Returns jwt token if username and password match, otherwise returns error","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginData"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Token"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"}},"summary":"Login","tags":["Users"]}},"/api/user/register":{"post":{"description":"Registers user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterData"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Users"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"}},"summary":"Register","tags":["Users"]}},"/api/user/setAdmin":{"put":{"description":"Set admin state of specified user","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminData"}}}},"responses":{"204":{"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Set user admin state","tags":["Users"]}},"/api/users":{"get":{"description":"Returns all existing users as array","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Users"},"type":"array"},"status":{"type":"integer"},"text":{"type":"string"}},"type":"object"}}},"description":"Successful response"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPError"}}},"description":"Authentication error"}},"security":[{"BearerAuth":[]}],"summary":"Get all users","tags":["Users"]}}},"servers":[{"name":"Production","url":"https://aktienbot.flokaiser.com"},{"name":"Local","url":"http://127.0.0.1:5000"}],"tags":[{"name":"Keyword"},{"name":"Share"},{"name":"Transaction"},{"name":"Portfolio"},{"name":"Users"},{"name":"Telegram"}]} +{ + "components": { + "schemas": { + "AdminData": { + "properties": { + "admin": { + "type": "boolean" + }, + "email": { + "format": "email", + "type": "string" + } + }, + "type": "object" + }, + "CronData": { + "properties": { + "cron": { + "type": "string" + } + }, + "type": "object" + }, + "DeleteSuccessful": { + "properties": {}, + "type": "object" + }, + "DeleteUser": { + "properties": { + "email": { + "format": "email", + "type": "string" + } + }, + "type": "object" + }, + "HTTPError": { + "properties": { + "detail": { + "type": "object" + }, + "message": { + "type": "string" + } + }, + "type": "object" + }, + "Keyword": { + "properties": { + "keyword": { + "type": "string" + } + }, + "type": "object" + }, + "KeywordResponse": { + "properties": { + "email": { + "format": "email", + "type": "string" + }, + "keyword": { + "type": "string" + }, + "s_id": { + "type": "integer" + } + }, + "type": "object" + }, + "LoginData": { + "properties": { + "email": { + "format": "email", + "type": "string" + }, + "password": { + "type": "string" + } + }, + "type": "object" + }, + "PortfolioResponse": { + "properties": { + "comment": { + "type": "string" + }, + "count": { + "type": "integer" + }, + "isin": { + "type": "string" + }, + "last_transaction": { + "type": "string" + } + }, + "type": "object" + }, + "RegisterData": { + "properties": { + "email": { + "format": "email", + "type": "string" + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "Symbol": { + "properties": { + "comment": { + "type": "string" + }, + "isin": { + "type": "string" + } + }, + "type": "object" + }, + "SymbolPrice": { + "properties": { + "isin": { + "type": "string" + }, + "price": { + "type": "number" + }, + "time": { + "pattern": "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z", + "type": "string" + } + }, + "type": "object" + }, + "SymbolRemove": { + "properties": { + "isin": { + "type": "string" + } + }, + "type": "object" + }, + "SymbolResponse": { + "properties": { + "comment": { + "type": "string" + }, + "email": { + "format": "email", + "type": "string" + }, + "isin": { + "type": "string" + }, + "s_id": { + "type": "integer" + } + }, + "type": "object" + }, + "TelegramId": { + "properties": { + "telegram_user_id": { + "type": "string" + } + }, + "type": "object" + }, + "Token": { + "properties": { + "token": { + "type": "string" + } + }, + "type": "object" + }, + "Transaction": { + "properties": { + "comment": { + "type": "string" + }, + "count": { + "type": "integer" + }, + "isin": { + "type": "string" + }, + "price": { + "type": "number" + }, + "time": { + "pattern": "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z", + "type": "string" + } + }, + "type": "object" + }, + "TransactionResponse": { + "properties": { + "comment": { + "type": "string" + }, + "count": { + "type": "integer" + }, + "email": { + "format": "email", + "type": "string" + }, + "isin": { + "type": "string" + }, + "price": { + "type": "number" + }, + "time": { + "type": "string" + } + }, + "type": "object" + }, + "UpdateUserData": { + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "Users": { + "properties": { + "admin": { + "type": "boolean" + }, + "cron": { + "type": "string" + }, + "email": { + "format": "email", + "type": "string" + }, + "password": { + "type": "string" + }, + "telegram_user_id": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "ValidationError": { + "properties": { + "detail": { + "properties": { + "": { + "properties": { + "": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "message": { + "type": "string" + } + }, + "type": "object" + } + }, + "securitySchemes": { + "BearerAuth": { + "scheme": "Bearer", + "type": "http" + } + } + }, + "info": { + "title": "APIFlask", + "version": "0.1.0" + }, + "openapi": "3.0.3", + "paths": { + "/api/keyword": { + "delete": { + "description": "Removes existing keyword for current user", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Keyword" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/DeleteSuccessful" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + }, + "description": "Validation error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Removes existing keyword", + "tags": [ + "Keyword" + ] + }, + "post": { + "description": "Adds new keyword for current user", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Keyword" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/KeywordResponse" + }, + "type": "array" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + }, + "description": "Validation error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Add new keyword", + "tags": [ + "Keyword" + ] + } + }, + "/api/keywords": { + "get": { + "description": "Returns all keywords for current user", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/KeywordResponse" + }, + "type": "array" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Returns all keywords", + "tags": [ + "Keyword" + ] + } + }, + "/api/portfolio": { + "get": { + "description": "Returns all shares of current user", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/PortfolioResponse" + }, + "type": "array" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Returns portfolio", + "tags": [ + "Portfolio" + ] + } + }, + "/api/share": { + "delete": { + "description": "Removes existing symbol for current user", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SymbolRemove" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/DeleteSuccessful" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + }, + "description": "Validation error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Removes existing symbol", + "tags": [ + "Share" + ] + }, + "post": { + "description": "Adds new symbol for current user", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Symbol" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/SymbolResponse" + }, + "type": "array" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + }, + "description": "Validation error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Add new symbol", + "tags": [ + "Share" + ] + } + }, + "/api/shares": { + "get": { + "description": "Returns all symbols for current user", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/SymbolResponse" + }, + "type": "array" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Returns all symbols", + "tags": [ + "Share" + ] + } + }, + "/api/symbol": { + "post": { + "description": "Adds new price to database", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SymbolPrice" + } + } + } + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + }, + "description": "Validation error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Adds new price for isin", + "tags": [ + "Share_Price" + ] + } + }, + "/api/symbols": { + "get": { + "description": "Returns all transaction symbols for all users", + "parameters": [], + "responses": { + "204": { + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Returns all transaction symbols", + "tags": [ + "Share_Price" + ] + } + }, + "/api/telegram": { + "post": { + "description": "Connects telegram user id to user account", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TelegramId" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/Users" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + }, + "description": "Validation error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Connects telegram user id", + "tags": [ + "Telegram" + ] + } + }, + "/api/transaction": { + "post": { + "description": "Adds new transaction for current user", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Transaction" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/TransactionResponse" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + }, + "description": "Validation error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Adds new transaction", + "tags": [ + "Transaction" + ] + } + }, + "/api/transactions": { + "get": { + "description": "Returns all transactions for current user", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/Transaction" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Returns all transactions", + "tags": [ + "Transaction" + ] + } + }, + "/api/user": { + "delete": { + "description": "Deletes user by username", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteUser" + } + } + } + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + }, + "description": "Validation error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Delete user", + "tags": [ + "Users" + ] + }, + "get": { + "description": "Returns current user", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/Users" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Get current user", + "tags": [ + "Users" + ] + }, + "put": { + "description": "Changes password and/or username of current user", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateUserData" + } + } + } + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + }, + "description": "Validation error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Update user", + "tags": [ + "Users" + ] + } + }, + "/api/user/login": { + "post": { + "description": "Returns jwt token if username and password match, otherwise returns error", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginData" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/Token" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + }, + "description": "Validation error" + } + }, + "summary": "Login", + "tags": [ + "Users" + ] + } + }, + "/api/user/register": { + "post": { + "description": "Registers user", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterData" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "$ref": "#/components/schemas/Users" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + }, + "description": "Validation error" + } + }, + "summary": "Register", + "tags": [ + "Users" + ] + } + }, + "/api/user/setAdmin": { + "put": { + "description": "Set admin state of specified user", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminData" + } + } + } + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + }, + "description": "Validation error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Set user admin state", + "tags": [ + "Users" + ] + } + }, + "/api/user/setCron": { + "put": { + "description": "Set update cron of specified user", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CronData" + } + } + } + }, + "responses": { + "204": { + "description": "Successful response" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationError" + } + } + }, + "description": "Validation error" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Set update cron", + "tags": [ + "Users" + ] + } + }, + "/api/users": { + "get": { + "description": "Returns all existing users as array", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/Users" + }, + "type": "array" + }, + "status": { + "type": "integer" + }, + "text": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "Successful response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPError" + } + } + }, + "description": "Authentication error" + } + }, + "security": [ + { + "BearerAuth": [] + } + ], + "summary": "Get all users", + "tags": [ + "Users" + ] + } + } + }, + "tags": [ + { + "name": "Keyword" + }, + { + "name": "Share" + }, + { + "name": "Share_Price" + }, + { + "name": "Transaction" + }, + { + "name": "Portfolio" + }, + { + "name": "Users" + }, + { + "name": "Telegram" + } + ] +} diff --git a/documentation/api/postman.json b/documentation/api/postman.json index db6e014..994ca6e 100644 --- a/documentation/api/postman.json +++ b/documentation/api/postman.json @@ -1,10 +1,9 @@ { "info": { - "_postman_id": "eed3eb48-b932-4d72-979a-38de9ae2cbf0", - "name": "APIFlask", - "description": "Webengineering 2 | Telegram Aktienbot", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, + "_postman_id": "ab12ad1f-aaf8-44c2-80de-bd4e1ec72c4b", + "name": "APIFlask", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, "item": [ { "name": "Keyword", @@ -34,14 +33,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"keyword\": \"nisi\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"keyword\": \"veniam pariatur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/keyword", "host": [ @@ -67,14 +66,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"keyword\": \"nisi\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"keyword\": \"veniam pariatur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/keyword", "host": [ @@ -96,7 +95,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {},\n \"status\": -35854777,\n \"text\": \"sint in\"\n}" + "body": "{\n \"data\": {},\n \"status\": -6906184,\n \"text\": \"velit ad reprehenderit quis\"\n}" }, { "name": "Validation error", @@ -110,14 +109,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"keyword\": \"nisi\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"keyword\": \"veniam pariatur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/keyword", "host": [ @@ -139,7 +138,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"in esse ut consectetur\",\n \"dolor dolore\"\n ]\n }\n },\n \"message\": \"tempor enim magna in\"\n}" }, { "name": "Authentication error", @@ -153,14 +152,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"keyword\": \"nisi\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"keyword\": \"veniam pariatur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/keyword", "host": [ @@ -182,7 +181,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" } ] }, @@ -211,14 +210,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"keyword\": \"nisi\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"keyword\": \"veniam pariatur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/keyword", "host": [ @@ -244,14 +243,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"keyword\": \"nisi\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"keyword\": \"veniam pariatur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/keyword", "host": [ @@ -273,7 +272,7 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"email\": \"8Es0sp5sb@wvQoAOvD.bzz\",\n \"keyword\": \"eiusmo\",\n \"s_id\": -48384117\n },\n {\n \"email\": \"MZ0cnQ1mwRV@oONtzNaRnVSaUXAReoDzwztMr.fiz\",\n \"keyword\": \"ullamco ad\",\n \"s_id\": -36966818\n }\n ],\n \"status\": 55335664,\n \"text\": \"dolor id ex\"\n}" + "body": "{\n \"data\": [\n {\n \"email\": \"4zcg8Ae@mFFZbwrszRgeKi.bmly\",\n \"keyword\": \"laboris\",\n \"s_id\": -29368814\n },\n {\n \"email\": \"WcrLS@cPNpWwtFqRDxUEumdjOsPLukcg.lmxg\",\n \"keyword\": \"veniam\",\n \"s_id\": 58969388\n }\n ],\n \"status\": -62012539,\n \"text\": \"ut incididunt sunt\"\n}" }, { "name": "Validation error", @@ -287,14 +286,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"keyword\": \"nisi\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"keyword\": \"veniam pariatur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/keyword", "host": [ @@ -316,7 +315,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"in esse ut consectetur\",\n \"dolor dolore\"\n ]\n }\n },\n \"message\": \"tempor enim magna in\"\n}" }, { "name": "Authentication error", @@ -330,14 +329,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"keyword\": \"nisi\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"keyword\": \"veniam pariatur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/keyword", "host": [ @@ -359,7 +358,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" } ] }, @@ -428,7 +427,7 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"email\": \"8Es0sp5sb@wvQoAOvD.bzz\",\n \"keyword\": \"eiusmo\",\n \"s_id\": -48384117\n },\n {\n \"email\": \"MZ0cnQ1mwRV@oONtzNaRnVSaUXAReoDzwztMr.fiz\",\n \"keyword\": \"ullamco ad\",\n \"s_id\": -36966818\n }\n ],\n \"status\": 55335664,\n \"text\": \"dolor id ex\"\n}" + "body": "{\n \"data\": [\n {\n \"email\": \"4zcg8Ae@mFFZbwrszRgeKi.bmly\",\n \"keyword\": \"laboris\",\n \"s_id\": -29368814\n },\n {\n \"email\": \"WcrLS@cPNpWwtFqRDxUEumdjOsPLukcg.lmxg\",\n \"keyword\": \"veniam\",\n \"s_id\": 58969388\n }\n ],\n \"status\": -62012539,\n \"text\": \"ut incididunt sunt\"\n}" }, { "name": "Authentication error", @@ -462,7 +461,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" } ] } @@ -496,14 +495,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"isin\": \"quis pariatur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/share", "host": [ @@ -529,14 +528,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"isin\": \"quis pariatur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/share", "host": [ @@ -558,7 +557,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {},\n \"status\": -35854777,\n \"text\": \"sint in\"\n}" + "body": "{\n \"data\": {},\n \"status\": -6906184,\n \"text\": \"velit ad reprehenderit quis\"\n}" }, { "name": "Validation error", @@ -572,14 +571,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"isin\": \"quis pariatur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/share", "host": [ @@ -601,7 +600,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"in esse ut consectetur\",\n \"dolor dolore\"\n ]\n }\n },\n \"message\": \"tempor enim magna in\"\n}" }, { "name": "Authentication error", @@ -615,14 +614,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"isin\": \"quis pariatur\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/share", "host": [ @@ -644,7 +643,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" } ] }, @@ -673,14 +672,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"comment\": \"irure adipisicing consectetur\",\n \"isin\": \"non irure ea labore\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/share", "host": [ @@ -706,14 +705,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"comment\": \"irure adipisicing consectetur\",\n \"isin\": \"non irure ea labore\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/share", "host": [ @@ -735,7 +734,7 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"email\": \"j32mUuC@iIJKfYQWSepoVsngEZYbfEmOwCzXXxVMP.alp\",\n \"s_id\": 86469214,\n \"symbol\": \"labore ea\"\n },\n {\n \"email\": \"PwKotAxaLBa8nY@nSsGmaxZeZqnRhOGmHhbICYhqTGLESrq.xy\",\n \"s_id\": 63768957,\n \"symbol\": \"laboris pariatur commodo\"\n }\n ],\n \"status\": -1862021,\n \"text\": \"culpa nulla\"\n}" + "body": "{\n \"data\": [\n {\n \"comment\": \"aute eu id\",\n \"email\": \"7Wq8YgGHE@T.phx\",\n \"isin\": \"amet anim Duis proident dolore\",\n \"s_id\": 87738546\n },\n {\n \"comment\": \"ut mollit\",\n \"email\": \"MT8jKX-7mq@gxctz.tskk\",\n \"isin\": \"in nost\",\n \"s_id\": -16197478\n }\n ],\n \"status\": -39445086,\n \"text\": \"aliqua consequat aute officia\"\n}" }, { "name": "Validation error", @@ -749,14 +748,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"comment\": \"irure adipisicing consectetur\",\n \"isin\": \"non irure ea labore\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/share", "host": [ @@ -778,7 +777,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"in esse ut consectetur\",\n \"dolor dolore\"\n ]\n }\n },\n \"message\": \"tempor enim magna in\"\n}" }, { "name": "Authentication error", @@ -792,14 +791,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"symbol\": \"magna Excepteur ullamco aute velit\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"comment\": \"irure adipisicing consectetur\",\n \"isin\": \"non irure ea labore\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/share", "host": [ @@ -821,7 +820,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" } ] }, @@ -890,7 +889,7 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"email\": \"j32mUuC@iIJKfYQWSepoVsngEZYbfEmOwCzXXxVMP.alp\",\n \"s_id\": 86469214,\n \"symbol\": \"labore ea\"\n },\n {\n \"email\": \"PwKotAxaLBa8nY@nSsGmaxZeZqnRhOGmHhbICYhqTGLESrq.xy\",\n \"s_id\": 63768957,\n \"symbol\": \"laboris pariatur commodo\"\n }\n ],\n \"status\": -1862021,\n \"text\": \"culpa nulla\"\n}" + "body": "{\n \"data\": [\n {\n \"comment\": \"aute eu id\",\n \"email\": \"7Wq8YgGHE@T.phx\",\n \"isin\": \"amet anim Duis proident dolore\",\n \"s_id\": 87738546\n },\n {\n \"comment\": \"ut mollit\",\n \"email\": \"MT8jKX-7mq@gxctz.tskk\",\n \"isin\": \"in nost\",\n \"s_id\": -16197478\n }\n ],\n \"status\": -39445086,\n \"text\": \"aliqua consequat aute officia\"\n}" }, { "name": "Authentication error", @@ -914,27 +913,312 @@ ] } }, - "status": "Unauthorized", - "code": 401, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" - } - ] - } - ] - }, - { - "name": "Transaction", - "item": [ - { - "name": "Adds new transaction", + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" + } + ] + } + ] + }, + { + "name": "Share_Price", + "item": [ + { + "name": "Adds new price for isin", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"isin\": \"consequat ut elit incididunt\",\n \"price\": 8596518.682177305,\n \"time\": \"6953-98-62T41:25:08.545Z\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/symbol", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "symbol" + ] + }, + "description": "Adds new price to database" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"isin\": \"consequat ut elit incididunt\",\n \"price\": 8596518.682177305,\n \"time\": \"6953-98-62T41:25:08.545Z\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/symbol", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "symbol" + ] + } + }, + "status": "No Content", + "code": 204, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "cookie": [], + "body": "" + }, + { + "name": "Validation error", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"isin\": \"consequat ut elit incididunt\",\n \"price\": 8596518.682177305,\n \"time\": \"6953-98-62T41:25:08.545Z\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/symbol", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "symbol" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"in esse ut consectetur\",\n \"dolor dolore\"\n ]\n }\n },\n \"message\": \"tempor enim magna in\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "POST", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"isin\": \"consequat ut elit incididunt\",\n \"price\": 8596518.682177305,\n \"time\": \"6953-98-62T41:25:08.545Z\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/symbol", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "symbol" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" + } + ] + }, + { + "name": "Returns all transaction symbols", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{baseUrl}}/api/symbols", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "symbols" + ] + }, + "description": "Returns all transaction symbols for all users" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/symbols", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "symbols" + ] + } + }, + "status": "No Content", + "code": 204, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "cookie": [], + "body": "" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "GET", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "url": { + "raw": "{{baseUrl}}/api/symbols", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "symbols" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" + } + ] + } + ] + }, + { + "name": "Transaction", + "item": [ + { + "name": "Adds new transaction", "request": { "auth": { "type": "bearer", @@ -958,14 +1242,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"count\": 76782033,\n \"price\": -9709838.042264134,\n \"symbol\": \"mollit esse ex in\",\n \"time\": \"2120-69-63T43:76:75.378Z\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"comment\": \"non nulla\",\n \"count\": 96442090,\n \"isin\": \"non esse\",\n \"price\": -17020893.639093384,\n \"time\": \"9660-60-73T53:64:02.637Z\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/transaction", "host": [ @@ -991,14 +1275,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"count\": 76782033,\n \"price\": -9709838.042264134,\n \"symbol\": \"mollit esse ex in\",\n \"time\": \"2120-69-63T43:76:75.378Z\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"comment\": \"non nulla\",\n \"count\": 96442090,\n \"isin\": \"non esse\",\n \"price\": -17020893.639093384,\n \"time\": \"9660-60-73T53:64:02.637Z\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/transaction", "host": [ @@ -1020,7 +1304,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {\n \"count\": 99691073,\n \"email\": \"DAX6XjHTr8eA6M@xsoUe.id\",\n \"price\": -14617384.086570382,\n \"symbol\": \"eu ad deserunt\",\n \"time\": \"ipsum nisi in laboris\"\n },\n \"status\": -51137224,\n \"text\": \"ipsum culpa amet\"\n}" + "body": "{\n \"data\": {\n \"comment\": \"ex cillum ullamco officia\",\n \"count\": -23493756,\n \"email\": \"fmL6Vhy@daFGKga.aton\",\n \"isin\": \"aliquip enim sint\",\n \"price\": -46738573.8885945,\n \"time\": \"aute enim\"\n },\n \"status\": -46966272,\n \"text\": \"adipisicing qui amet nulla\"\n}" }, { "name": "Validation error", @@ -1034,14 +1318,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"count\": 76782033,\n \"price\": -9709838.042264134,\n \"symbol\": \"mollit esse ex in\",\n \"time\": \"2120-69-63T43:76:75.378Z\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"comment\": \"non nulla\",\n \"count\": 96442090,\n \"isin\": \"non esse\",\n \"price\": -17020893.639093384,\n \"time\": \"9660-60-73T53:64:02.637Z\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/transaction", "host": [ @@ -1063,7 +1347,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"in esse ut consectetur\",\n \"dolor dolore\"\n ]\n }\n },\n \"message\": \"tempor enim magna in\"\n}" }, { "name": "Authentication error", @@ -1077,14 +1361,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"count\": 76782033,\n \"price\": -9709838.042264134,\n \"symbol\": \"mollit esse ex in\",\n \"time\": \"2120-69-63T43:76:75.378Z\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"comment\": \"non nulla\",\n \"count\": 96442090,\n \"isin\": \"non esse\",\n \"price\": -17020893.639093384,\n \"time\": \"9660-60-73T53:64:02.637Z\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/transaction", "host": [ @@ -1106,7 +1390,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" } ] }, @@ -1175,7 +1459,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {\n \"count\": -37878076,\n \"price\": -40419772.172105886,\n \"symbol\": \"velit ut Ut elit esse\",\n \"time\": \"5462-92-97T03:27:22.076Z\"\n },\n \"status\": -509477,\n \"text\": \"proident\"\n}" + "body": "{\n \"data\": {\n \"comment\": \"ex ipsum adipisicing dolor\",\n \"count\": 46881527,\n \"isin\": \"ad eiusmod\",\n \"price\": -11144262.12990503,\n \"time\": \"5529-43-74T12:79:94.205Z\"\n },\n \"status\": 25470796,\n \"text\": \"exercitation et est\"\n}" }, { "name": "Authentication error", @@ -1209,7 +1493,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" } ] } @@ -1283,7 +1567,7 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"count\": 75537465,\n \"last_transaction\": \"dolor enim\",\n \"symbol\": \"enim nostrud non deserunt\"\n },\n {\n \"count\": -69490418,\n \"last_transaction\": \"et\",\n \"symbol\": \"Excepteur irure est enim\"\n }\n ],\n \"status\": 7099250,\n \"text\": \"dolore in ad\"\n}" + "body": "{\n \"data\": [\n {\n \"comment\": \"sint quis ut\",\n \"count\": 54930744,\n \"isin\": \"labore cillum\",\n \"last_transaction\": \"id cillum non\"\n },\n {\n \"comment\": \"cillum ut ipsum\",\n \"count\": 44991486,\n \"isin\": \"in in consectetur velit\",\n \"last_transaction\": \"anim veniam\"\n }\n ],\n \"status\": 87593619,\n \"text\": \"non culpa occaecat\"\n}" }, { "name": "Authentication error", @@ -1317,7 +1601,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" } ] } @@ -1351,14 +1635,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"email\": \"HqkZQ@KMWQFPMhWGvBHrdzOdOampAHweUvPCej.kvbl\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"email\": \"oG8z5swOjkbst@kinVwpTMhsrUUCewsWdTkNXO.pqv\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user", "host": [ @@ -1384,14 +1668,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"email\": \"HqkZQ@KMWQFPMhWGvBHrdzOdOampAHweUvPCej.kvbl\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"email\": \"oG8z5swOjkbst@kinVwpTMhsrUUCewsWdTkNXO.pqv\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user", "host": [ @@ -1427,14 +1711,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"email\": \"HqkZQ@KMWQFPMhWGvBHrdzOdOampAHweUvPCej.kvbl\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"email\": \"oG8z5swOjkbst@kinVwpTMhsrUUCewsWdTkNXO.pqv\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user", "host": [ @@ -1456,7 +1740,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"in esse ut consectetur\",\n \"dolor dolore\"\n ]\n }\n },\n \"message\": \"tempor enim magna in\"\n}" }, { "name": "Authentication error", @@ -1470,14 +1754,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"email\": \"HqkZQ@KMWQFPMhWGvBHrdzOdOampAHweUvPCej.kvbl\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"email\": \"oG8z5swOjkbst@kinVwpTMhsrUUCewsWdTkNXO.pqv\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user", "host": [ @@ -1499,7 +1783,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" } ] }, @@ -1568,7 +1852,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {\n \"admin\": false,\n \"email\": \"VNB@NeqjbTmHnkWDwPacdUoXgpYVTNlD.iip\",\n \"password\": \"fugiat ipsum\",\n \"telegram_user_id\": \"et\",\n \"username\": \"nostrud laborum\"\n },\n \"status\": -78288268,\n \"text\": \"laborum minim\"\n}" + "body": "{\n \"data\": {\n \"admin\": true,\n \"cron\": \"id in Excepteur\",\n \"email\": \"E3rj@zUSyCWAiewlz.vrx\",\n \"password\": \"sunt pariatur\",\n \"telegram_user_id\": \"elit\",\n \"username\": \"mollit incididunt nostrud\"\n },\n \"status\": -87469860,\n \"text\": \"ullamco in\"\n}" }, { "name": "Authentication error", @@ -1602,7 +1886,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" } ] }, @@ -1631,14 +1915,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"password\": \"elit do in esse\",\n \"username\": \"mollit velit sed reprehenderit\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"password\": \"ut\",\n \"username\": \"ullamco amet\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user", "host": [ @@ -1664,14 +1948,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"password\": \"elit do in esse\",\n \"username\": \"mollit velit sed reprehenderit\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"password\": \"ut\",\n \"username\": \"ullamco amet\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user", "host": [ @@ -1707,14 +1991,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"password\": \"elit do in esse\",\n \"username\": \"mollit velit sed reprehenderit\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"password\": \"ut\",\n \"username\": \"ullamco amet\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user", "host": [ @@ -1736,7 +2020,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"in esse ut consectetur\",\n \"dolor dolore\"\n ]\n }\n },\n \"message\": \"tempor enim magna in\"\n}" }, { "name": "Authentication error", @@ -1750,14 +2034,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"password\": \"elit do in esse\",\n \"username\": \"mollit velit sed reprehenderit\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"password\": \"ut\",\n \"username\": \"ullamco amet\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user", "host": [ @@ -1779,7 +2063,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" } ] }, @@ -1798,14 +2082,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"email\": \"YKQkCdcOoJ3vH@wbOpjGTXH.avm\",\n \"password\": \"veniam\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"email\": \"GfEubxoHYHPPc4@PXsBSXusLxIotbYTjEAaQnAVHLDz.jv\",\n \"password\": \"Duis ut\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user/login", "host": [ @@ -1826,14 +2110,14 @@ "method": "POST", "header": [], "body": { - "mode": "raw", - "raw": "{\n \"email\": \"YKQkCdcOoJ3vH@wbOpjGTXH.avm\",\n \"password\": \"veniam\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"email\": \"GfEubxoHYHPPc4@PXsBSXusLxIotbYTjEAaQnAVHLDz.jv\",\n \"password\": \"Duis ut\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user/login", "host": [ @@ -1856,7 +2140,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {\n \"token\": \"do ipsu\"\n },\n \"status\": 31961350,\n \"text\": \"Lorem dolor commodo laborum cillum\"\n}" + "body": "{\n \"data\": {\n \"token\": \"non\"\n },\n \"status\": 92819984,\n \"text\": \"consectetur elit esse non\"\n}" }, { "name": "Validation error", @@ -1864,14 +2148,14 @@ "method": "POST", "header": [], "body": { - "mode": "raw", - "raw": "{\n \"email\": \"YKQkCdcOoJ3vH@wbOpjGTXH.avm\",\n \"password\": \"veniam\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"email\": \"GfEubxoHYHPPc4@PXsBSXusLxIotbYTjEAaQnAVHLDz.jv\",\n \"password\": \"Duis ut\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user/login", "host": [ @@ -1894,7 +2178,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"in esse ut consectetur\",\n \"dolor dolore\"\n ]\n }\n },\n \"message\": \"tempor enim magna in\"\n}" } ] }, @@ -1913,14 +2197,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"email\": \"DJR029Ov-keM@pyhCxnpZmcVMxADSiCjmsGRZksCf.zz\",\n \"password\": \"sed voluptate\",\n \"username\": \"voluptate aute proident\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"email\": \"3zVMb95E@jZETuCbGKKbkmiNrTNNfdpTPQdkBJNbzD.wicb\",\n \"password\": \"eiusmod cillum\",\n \"username\": \"enim\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user/register", "host": [ @@ -1941,14 +2225,14 @@ "method": "POST", "header": [], "body": { - "mode": "raw", - "raw": "{\n \"email\": \"DJR029Ov-keM@pyhCxnpZmcVMxADSiCjmsGRZksCf.zz\",\n \"password\": \"sed voluptate\",\n \"username\": \"voluptate aute proident\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"email\": \"3zVMb95E@jZETuCbGKKbkmiNrTNNfdpTPQdkBJNbzD.wicb\",\n \"password\": \"eiusmod cillum\",\n \"username\": \"enim\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user/register", "host": [ @@ -1971,7 +2255,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {\n \"admin\": false,\n \"email\": \"VNB@NeqjbTmHnkWDwPacdUoXgpYVTNlD.iip\",\n \"password\": \"fugiat ipsum\",\n \"telegram_user_id\": \"et\",\n \"username\": \"nostrud laborum\"\n },\n \"status\": -78288268,\n \"text\": \"laborum minim\"\n}" + "body": "{\n \"data\": {\n \"admin\": true,\n \"cron\": \"id in Excepteur\",\n \"email\": \"E3rj@zUSyCWAiewlz.vrx\",\n \"password\": \"sunt pariatur\",\n \"telegram_user_id\": \"elit\",\n \"username\": \"mollit incididunt nostrud\"\n },\n \"status\": -87469860,\n \"text\": \"ullamco in\"\n}" }, { "name": "Validation error", @@ -1979,14 +2263,14 @@ "method": "POST", "header": [], "body": { - "mode": "raw", - "raw": "{\n \"email\": \"DJR029Ov-keM@pyhCxnpZmcVMxADSiCjmsGRZksCf.zz\",\n \"password\": \"sed voluptate\",\n \"username\": \"voluptate aute proident\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"email\": \"3zVMb95E@jZETuCbGKKbkmiNrTNNfdpTPQdkBJNbzD.wicb\",\n \"password\": \"eiusmod cillum\",\n \"username\": \"enim\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user/register", "host": [ @@ -2009,7 +2293,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"in esse ut consectetur\",\n \"dolor dolore\"\n ]\n }\n },\n \"message\": \"tempor enim magna in\"\n}" } ] }, @@ -2038,14 +2322,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"admin\": false,\n \"email\": \"qUgn5u1bWI4cwc@LjiesdFcoYwvweQto.dj\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"admin\": false,\n \"email\": \"3lnXNx5@NhoXHfuhjTiz.bgre\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user/setAdmin", "host": [ @@ -2072,14 +2356,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"admin\": false,\n \"email\": \"qUgn5u1bWI4cwc@LjiesdFcoYwvweQto.dj\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"admin\": false,\n \"email\": \"3lnXNx5@NhoXHfuhjTiz.bgre\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user/setAdmin", "host": [ @@ -2116,14 +2400,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"admin\": false,\n \"email\": \"qUgn5u1bWI4cwc@LjiesdFcoYwvweQto.dj\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"admin\": false,\n \"email\": \"3lnXNx5@NhoXHfuhjTiz.bgre\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user/setAdmin", "host": [ @@ -2146,7 +2430,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"anim quis\",\n \"in velit ali\"\n ]\n }\n },\n \"message\": \"nostrud\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"in esse ut consectetur\",\n \"dolor dolore\"\n ]\n }\n },\n \"message\": \"tempor enim magna in\"\n}" }, { "name": "Authentication error", @@ -2160,14 +2444,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"admin\": false,\n \"email\": \"qUgn5u1bWI4cwc@LjiesdFcoYwvweQto.dj\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"admin\": false,\n \"email\": \"3lnXNx5@NhoXHfuhjTiz.bgre\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/user/setAdmin", "host": [ @@ -2180,27 +2464,208 @@ ] } }, - "status": "Unauthorized", - "code": 401, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" - } - ] - }, - { - "name": "Get all users", - "request": { - "auth": { - "type": "bearer", - "bearer": [ - { + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" + } + ] + }, + { + "name": "Set update cron", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"cron\": \"dolor\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/setCron", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "setCron" + ] + }, + "description": "Set update cron of specified user" + }, + "response": [ + { + "name": "Successful response", + "originalRequest": { + "method": "PUT", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"cron\": \"dolor\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/setCron", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "setCron" + ] + } + }, + "status": "No Content", + "code": 204, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Content-Type", + "value": "text/plain" + } + ], + "cookie": [], + "body": "" + }, + { + "name": "Validation error", + "originalRequest": { + "method": "PUT", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"cron\": \"dolor\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/setCron", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "setCron" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"in esse ut consectetur\",\n \"dolor dolore\"\n ]\n }\n },\n \"message\": \"tempor enim magna in\"\n}" + }, + { + "name": "Authentication error", + "originalRequest": { + "method": "PUT", + "header": [ + { + "description": "Added as a part of security scheme: bearer", + "key": "Authorization", + "value": "Bearer " + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"cron\": \"dolor\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/user/setCron", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "user", + "setCron" + ] + } + }, + "status": "Unauthorized", + "code": 401, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" + } + ] + }, + { + "name": "Get all users", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { "key": "token", "value": "", "type": "string" @@ -2259,7 +2724,7 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"admin\": false,\n \"email\": \"paghoo4COL6gQNz@SJXcMSOC.kus\",\n \"password\": \"exercitation laboris Ut cillum nisi\",\n \"telegram_user_id\": \"id in incididunt sint\",\n \"username\": \"sed laborum cill\"\n },\n {\n \"admin\": false,\n \"email\": \"WFYb2q0XXey6WZa@JNSezyFkIVzneuifIXi.la\",\n \"password\": \"velit irure veniam\",\n \"telegram_user_id\": \"pariatur sed commodo\",\n \"username\": \"incididunt consequat\"\n }\n ],\n \"status\": -92106336,\n \"text\": \"commodo ull\"\n}" + "body": "{\n \"data\": [\n {\n \"admin\": false,\n \"cron\": \"cupidatat nostrud\",\n \"email\": \"udtQkVtNcxr@OJMRrDDwMlfw.udu\",\n \"password\": \"dolore consectetur quis n\",\n \"telegram_user_id\": \"sint in\",\n \"username\": \"in minim ea labo\"\n },\n {\n \"admin\": false,\n \"cron\": \"enim exercitation laboris voluptate aute\",\n \"email\": \"jQyQftlI@zOMiqukOfCTGwRN.bp\",\n \"password\": \"non minim tempor laboris exercitation\",\n \"telegram_user_id\": \"ipsum Excepteur adipisicing\",\n \"username\": \"ad\"\n }\n ],\n \"status\": -85994498,\n \"text\": \"pariatur nostrud cupidatat\"\n}" }, { "name": "Authentication error", @@ -2293,7 +2758,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" } ] } @@ -2327,14 +2792,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"telegram_user_id\": \"do quis\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"telegram_user_id\": \"adipisicing ut occaecat\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/telegram", "host": [ @@ -2360,14 +2825,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"telegram_user_id\": \"do quis\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"telegram_user_id\": \"adipisicing ut occaecat\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/telegram", "host": [ @@ -2389,7 +2854,7 @@ } ], "cookie": [], - "body": "{\n \"data\": {\n \"admin\": false,\n \"email\": \"VNB@NeqjbTmHnkWDwPacdUoXgpYVTNlD.iip\",\n \"password\": \"fugiat ipsum\",\n \"telegram_user_id\": \"et\",\n \"username\": \"nostrud laborum\"\n },\n \"status\": -78288268,\n \"text\": \"laborum minim\"\n}" + "body": "{\n \"data\": {\n \"admin\": true,\n \"cron\": \"id in Excepteur\",\n \"email\": \"E3rj@zUSyCWAiewlz.vrx\",\n \"password\": \"sunt pariatur\",\n \"telegram_user_id\": \"elit\",\n \"username\": \"mollit incididunt nostrud\"\n },\n \"status\": -87469860,\n \"text\": \"ullamco in\"\n}" }, { "name": "Validation error", @@ -2403,14 +2868,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"telegram_user_id\": \"do quis\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"telegram_user_id\": \"adipisicing ut occaecat\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/telegram", "host": [ @@ -2432,7 +2897,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"velit\",\n \"ut laboris\"\n ]\n }\n },\n \"message\": \"dolore nostrud officia ex\"\n}" + "body": "{\n \"detail\": {\n \"\": {\n \"\": [\n \"tempor consectetur sint adipisicing\",\n \"occaecat laborum consectetur\"\n ]\n }\n },\n \"message\": \"dolor est officia\"\n}" }, { "name": "Authentication error", @@ -2446,14 +2911,14 @@ } ], "body": { - "mode": "raw", - "raw": "{\n \"telegram_user_id\": \"do quis\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, + "mode": "raw", + "raw": "{\n \"telegram_user_id\": \"adipisicing ut occaecat\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, "url": { "raw": "{{baseUrl}}/api/telegram", "host": [ @@ -2475,7 +2940,7 @@ } ], "cookie": [], - "body": "{\n \"detail\": {},\n \"message\": \"irure ad do quis\"\n}" + "body": "{\n \"detail\": {},\n \"message\": \"non elit\"\n}" } ] } @@ -2483,10 +2948,10 @@ } ], "variable": [ - { - "key": "baseUrl", - "value": "https://aktienbot.flokaiser.com", - "type": "string" - } + { + "key": "baseUrl", + "value": "/", + "type": "string" + } ] } \ No newline at end of file From e2be140c88fd1d288a400bea46f7ab26f38e2578 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Apr 2022 09:34:50 +0000 Subject: [PATCH 244/263] Bump @types/node from 17.0.25 to 17.0.27 in /frontend Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 17.0.25 to 17.0.27. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 90 +++----------------------------------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 84 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a9bfceb..40b4739 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -29,7 +29,7 @@ "@angular/cli": "~13.3.3", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.3", - "@types/node": "^17.0.25", + "@types/node": "^17.0.27", "jasmine-core": "~4.1.0", "karma": "~6.3.18", "karma-chrome-launcher": "~3.1.0", @@ -295,51 +295,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/@angular-devkit/core": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.3.tgz", - "integrity": "sha512-lfQwY9LuVRwcNVzGmyPcwOpb3CAobP4T+c3joR1LLIPS5lzcM0oeCE2bon9N52Ktn4Q/pH98dVtjWL+jSrUADw==", - "dev": true, - "dependencies": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/core/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/@angular-devkit/core/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/@angular-devkit/schematics": { "version": "13.3.3", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.3.tgz", @@ -2776,9 +2731,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "17.0.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", - "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==", + "version": "17.0.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.27.tgz", + "integrity": "sha512-4/Ke7bbWOasuT3kceBZFGakP1dYN2XFd8v2l9bqF2LNWrmeU07JLpp56aEeG6+Q3olqO5TvXpW0yaiYnZJ5CXg==", "dev": true }, "node_modules/@types/parse-json": { @@ -11672,37 +11627,6 @@ } } }, - "@angular-devkit/core": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.3.tgz", - "integrity": "sha512-lfQwY9LuVRwcNVzGmyPcwOpb3CAobP4T+c3joR1LLIPS5lzcM0oeCE2bon9N52Ktn4Q/pH98dVtjWL+jSrUADw==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "dependencies": { - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, "@angular-devkit/schematics": { "version": "13.3.3", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.3.tgz", @@ -13456,9 +13380,9 @@ "dev": true }, "@types/node": { - "version": "17.0.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz", - "integrity": "sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==", + "version": "17.0.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.27.tgz", + "integrity": "sha512-4/Ke7bbWOasuT3kceBZFGakP1dYN2XFd8v2l9bqF2LNWrmeU07JLpp56aEeG6+Q3olqO5TvXpW0yaiYnZJ5CXg==", "dev": true }, "@types/parse-json": { diff --git a/frontend/package.json b/frontend/package.json index 9cdd286..656e4f0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,7 +31,7 @@ "@angular/cli": "~13.3.3", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.3", - "@types/node": "^17.0.25", + "@types/node": "^17.0.27", "karma": "~6.3.18", "jasmine-core": "~4.1.0", "karma-chrome-launcher": "~3.1.0", From 42a5ad4a12399f16a47c3b6e7f3a163c8794a31d Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:07:56 +0200 Subject: [PATCH 245/263] more commenting and error correction in updater --- telegram_bot/bot.py | 3 ++- telegram_bot/bot_updates.py | 24 ++++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index f5c3bef..67baf98 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -118,7 +118,7 @@ def send_all_users(message): bot.send_message(chat_id=user_id, text=f'Username: {username}\nEmail: {email}\nID: {id}\nCron: {cron}\nAdmin: {admin}') # format user data into readable message text -@bot.message_handler(commands=['setAdmin', 'SetAdmin']) # set admin rights to user TBD: not working!! +@bot.message_handler(commands=['setAdmin', 'SetAdmin', 'setadmin', 'Setadmin']) # set admin rights to user TBD: not working!! def set_admin(message): """ Set admin rights to user @@ -150,6 +150,7 @@ def set_admin_step(message): email = args_message[0] is_admin = args_message[1] + status = api_handler.set_admin(email, is_admin) # set admin in db if(status == 200): diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 46df917..4853df0 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -6,17 +6,18 @@ __date__ = "05.04.2022" __version__ = "1.0.1" __license__ = "None" -from calendar import month -from symtable import Symbol +from asyncio.windows_events import NULL +from calendar import month # unused, remove? +from symtable import Symbol # unused, remove? from dotenv import load_dotenv -from shares.share_fetcher import get_share_price +from shares.share_fetcher import get_share_price # unused, remove? import news.news_fetcher as news_fetcher import time -import datetime +import datetime # unused, remove? import os from bot import bot import sys -from multiprocessing import Process +from multiprocessing import Process # unused, remove? from apscheduler.schedulers.background import BackgroundScheduler from api_handling.api_handler import API_Handler @@ -153,9 +154,16 @@ def update_for_user(p_user_id, p_my_handler): if(keywords): # if keywords exist and array is not empty send_to_user("If you haven't read yet: \nHere are some interesting news according to your keywords:", pUser_id=p_user_id) for keyword in keywords: - news = news_fetcher.get_top_news_by_keyword(keyword)["articles"][0] # only use the most popular news - news_formatted = news_fetcher.format_article(news) # format for message - send_to_user(f"_keyword: {keyword}_\n\n{news_formatted}", pUser_id=p_user_id, md_mode=True) # send news with related keyword in Markdown + news = news_fetcher.get_top_news_by_keyword(keyword)["articles"] + + if not news: # if empty news array + send_to_user(f"No news found for keyword _{keyword}_.", pUser_id=p_user_id, md_mode=True) + + if news == None: # if news is none + send_to_user(f"Server error for keyword _{keyword}_.", pUser_id=p_user_id, md_mode=True) + else: + news_formatted = news_fetcher.format_article(news[0]) # format for message, only use the most popular article + send_to_user(f"_keyword: {keyword}_\n\n{news_formatted}", pUser_id=p_user_id, md_mode=True) # send news with related keyword in Markdown From b347c106531e5c4bb632ff341d76cb2ba75c86a9 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:21:37 +0200 Subject: [PATCH 246/263] fixed /setadmin function --- telegram_bot/api_handling/api_handler.py | 5 +++-- telegram_bot/bot.py | 9 ++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index 181fd71..63cfcdf 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -9,6 +9,7 @@ __license__ = "None" #side-dependencies: none #Work in Progress +from email import header import sys import os import requests as r @@ -340,14 +341,14 @@ class API_Handler: Args: email (string): email of the user - is_admin (String): "true" if user should be Admin, "false" if not + is_admin (bool): "true" if user should be Admin, "false" if not Returns: int: status code """ with r.Session() as s: headers = {'Authorization': 'Bearer ' + self.token} # only bot token is needed, user is chosen by email - req = s.put(self.db_adress + "/user/setAdmin", json={"admin": str(is_admin),"email": str(email)}) + req = s.put(self.db_adress + "/user/setAdmin", json={"admin": is_admin,"email": str(email)}, headers=headers) return req.status_code diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 67baf98..331b29d 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -136,7 +136,7 @@ def set_admin(message): bot.reply_to(message, "You have to be an admin to use this command") return - bot.send_message(chat_id=user_id, text='send email and "true" if this account should have admin rights, else "false"\n in format: ,') # ask for email and admin rights to set + bot.send_message(chat_id=user_id, text='send email and true if this account should have admin rights, else false\n in format: ,') # request email and admin rights to change to bot.register_next_step_handler(message, set_admin_step) def set_admin_step(message): @@ -149,8 +149,11 @@ def set_admin_step(message): return email = args_message[0] - is_admin = args_message[1] - + is_admin = False # default: False + + if args_message[1].lower() == "true": # if user types true, set is_admin to true + is_admin = True + status = api_handler.set_admin(email, is_admin) # set admin in db if(status == 200): From bf526a174f499b48fbfe892b0740fce1f187d058 Mon Sep 17 00:00:00 2001 From: H4CK3R-01 Date: Tue, 26 Apr 2022 12:28:19 +0200 Subject: [PATCH 247/263] Update database structure --- documentation/database/structure_database.png | Bin 38191 -> 48733 bytes documentation/database/structure_database.uxf | 22 ++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/documentation/database/structure_database.png b/documentation/database/structure_database.png index d7d459d1b9e71e0d75fd6f7a1b955ba34ea40f17..426521555151e62160f3dd8d6ec1792e573f4bb1 100644 GIT binary patch literal 48733 zcmce;XH=7Iw=GOUfY1{NO%M`#l_tG~&`~-fNH2;gMT*jU=tvU;q)1aiQK|yc15y-e zDhMK7KvB9N{ao>R-hK8tdwl=S_sAX-?Y@qhaRO%FeM$dIO7R9W@k|M{N}sbeYQ z4PpP+H?YV)cBnpfi~MDq?*H*q#P4KAtZ@8~--&F&6v24Cj8~KX$M9f)!T)$z=o9dg-49pjqIVB9{k4PLKDR0_s)Gkb-Am zZ|{rUY&%?QE<65hfIV41JsCQBawBMz;s;q`1UG^UVymO5^mjRM0qASBIA5FkOEEDK zGq=lNWz{{GtgUC^e{6HerVTvWxQpO|P_np)wIMP&m&sWK_pJX}AqeH%tlPbkq==aq zHd|P|EBMhOIGz#m^Z1JL!yvi8L3Mn-91_m-pB zr95uIksHk^nGyI~8})i4S{>_zA&YiNC4x_ajV={PpH!S(-^ZnG>qk1 zq`z5S6_`^Zwi8oy=-Ww~D=Uu&)78<(;^YI=Wxj9ra>)&;9#-!6s+}Os8M#mG+Nq_t zu-7S=h-U?70}EdE$4)kQdD(>@->&ms=s9Y$oVjlXJ9@#xhqgKs8%?kJxn+Yuz4T`x zb!e2)_mtH?@A3;9x3Nkq=Rt+#{HgX9-I-Z`M`j`ANsqttg%THagsXz_wg)V)dW4Oq zGo*>$)s=#7RfQkFJNj-E{&w_B?RJif$Kas#j*ubPl8agf{~ljEeB~tHzNJnN`^sy^ zQ$I^MYld#+i}F9awhP&OvlKx}C&3kdRI|VJPTXLB>)WG;S;CjzLQ6(M@&DsO z`AVOY`|5XZg>OZr`Dbm?3OytLMF~V&;Pw)m@UXaj+tkljUp%jUu6BGJytg)*)^nFV zkBpi*XD0mg>(8d;!lkw5by>NoThT{7t#z(Lvcc=E4f{viLk7<*o?mPaJ06&DeJFOi z1maHc3(d#q504F`vu%75R!ntY-uN^i@GvAJkB(*E?D+51cpzvucTwl;?aZ`vu*H{j zyQ;Wv&xxWKOjLwgqkY<&_7cX(u2iijXR?k-oL)RIzYh{;&A1ayU=Ue*Oae^Tlc0#+BHzaS%`Su z@fWu5({(uAUbJlKONSi=jO6Zq^`H6NieTU2oi=-Xk|C~8Q2!CQJx(ic%AaL~{`)8qrjsIOJ+MNl%glJ*qJf29)i9T>#eT_kGIvh; zp3uFouG)9c@GXApq~JP#`^QJU&p$NiRm_a)(Ig4VAp>nWhn^v>p}>e<8HUm|6iTK^S`sc0eXiDt#WMf!GeH1YE*pKD5+ zwg;7*kzDnZP$=dPh!2kv!2M_q(1?H9fnH@Ul=hz2Oz6HflN6?jr&MH&wStifyTn~j ziKbK3-O29d3?4A!qGBj5Z~a0HYZ;Hy_qfpk(y!bn7=lUd$XX~HVcw)ty6m#<5{q#T z7>CX|)5=TF?oV|ⅇXg;SB6X%*4x)MS;uS4)<+eNKxv3|7Y=@(SyVF_L-31pDu6Z zi?{v35h@Mk=U^E0Ovv`#0G+9f_z0}S_0`cw7n~bLI4h|vMbYSh>(w@BueSZimV&x2 zmlIM!_^n_ShW?Gdi!31e`bO`89po*ZU(ra5px_MX@LL&H*To;AM0;kt=+!desn1@# z*2ld>Gr<)sHsFRYlhbo@@NgZ`Zq=Qh93~lRqA2!eBO{CazK?3{ z@Mrzx`5TD)8Fm-FmG`E6Ys{*RyG@3@S*~CnMxQzyi6(-OlqY=Ysjdz0fGQH=Eql+E zaalg(&n=8dBa>A({vzhmGm9+a^2Y{wZ=D`WI>!nu_%}?jMSbffErId=?qBeCe0<{4 zs_}1BWQ6&J55ig(_vYu&u;X71Gt3${xS?CW$L%km$R9x(DKJMI8R~`!hjuij0aosX zdM#rP8+=pL1l%-bI*0HCNGQa!;+s+k+XF%PSpYdg9O|?!j~0fHZ_BB{7GI9rF^==; zEmK9pIkCW zSAj+*4h!4h@1BvF>?f_gy=w8{g{=~07B?o5@eJH?O9Q)+e+vqmGe7)Um)i8R_MMBQ z)G#9V`-dm&K58i{yOu({e;V444dC!%Yf&49FM2Ie=C{NOk^Xr#=j5r8FRr}QH|&h6 zpDn=5MDOyPiOQPp-fWim5d3=j5i!|DaHpLE;p9n@&Cwy8M?jJEm|~73UxzEyd`l)H z#sR5KhZM4XUC(L{9;{GGE%1OS2T#;qU254Lytl$Yce20z@KluCe9LSk)S}W7<7kC< zNS`wFe6GLyZ=T}{4lUtEun+?n=D!E9Wfrec>+}D7NYnS|jyuAFH%HCl2V*GS@5G{`U?k8 zk+(1r^JOJq%sWT+489wPnV^=i5Tt_dRb{m2!bVwJyZMcV)j!0}>p zcnrn=&xsQ_>^*Krf)zP3U*D^r_`6h;C^!L@pTlll8u-6hjaM`Ju|a|=5f$yE4|D#F z1c-Cs0@95KT)#=h2x(u{rPOsyse1Jq3wBU~x;}W){qGE^h`UC8XQh4)L&R!{Fv$M0 z+Ns-(PfxFg9efuU!u(maKfQ6X^F%E-c&$l5Q=`nFUltn28B`v)JK_2sgwTg$rR}FD z!M*??JOHUG2c!|D=dPdg?85f1Ztu09+D4SX7%}Bt8_1DVLZ$&E`Tz{|^R}(vIYuxci+vZj$8F{J_Ro=abfn4d{^Ebx^^8H5jd~A(D1`%BB z+O!jgZmm|eKTz45GK-gfEkD}q??u|gsTS69w8W40n5>tiWtTm_@(1k2ZAvKNSpMwr zaX~k}{qU1-Ee`A1wtwwIjkC^oA||?77=99HGF?f5H!Sq-VS8-d;JI|)+B4xUf{LT4 z{^q#n6O)B2nQc@|!tojYhrb%A12-A`;mqhe0868^Z7doekmzev6KdprK2f>C$KJ5m zyVJ?fl57r!?tjakPw%>5QDMqw)_lifUx}I5WoaOHDUK^lg2gVNc%spJ!SFp#GS%f* zUr3$AT?(Bsw;0o6s97G2J-5yS3+OKV?(eun#S{ACFF8w$kb@?`;filSh8LBacN5@^ zU7@~!-H3naJE%~yx?xZgb)vxwPsi9rT?*=qA|vNv)t?2s?%~-u7mL?er0bkN%cGr~ z=8A|K*fY?mE|Gcn6=O-njA^#<=Yq~3(2TGms8(wh&bWX(wgmQ&o_R?^F*z^d z^J)MmQroknQ?X+^u6Rr+?00@81HwmQP-$JchL@2p+G%$#j_ZMu+OavS`B0(Ch4T#B zZ&a$q#WzkCcqySCH5;k_(lX~o)7=Z@7Lrf#G22ytjme2^?+7X-ODx+d`)64poi}JSxD!~$d8pwRq+u}bD2izLAO;K5bU!#;GF$9QJ>r$? z1eXP6<@67T9@#OWdCUO&M|bMZ7mrcoREi}Y&1VP0YvK7K^Gn^42r7RDuYR4UAWZ9H zsl4>vk^rSGibv?)CnbcPtcRa|$rQ7;XcG!xN24!fpJQ5Jm_gtyq>yw2x$@p6Zh$0A z^~zJi4+e=0pbv=+JK1;DfZu0GyQqx`*$YCZ&2`1~wp<-hQpO~)i0|^ES?Blf#v>Za zRv~l8@+=#LFm3FMrlm8KGD4SMs_dp|X76t=Uy!P@*NclN!ZN)0JsrIM z#x6Q0lB@#OExr{KSdbTy@AFm&7lmXNALApGeGNN^kV2#(CYJy z!#QQXkywY~!;R3+RYT57drUpqdb5F#K%#SB8it`0X_ODcYdXXuwe3V$@im6eD7x1L z@u!O7P@JDOh`5h71C-IlcoU{9itb#}c@FIhSkBNLlMjzBu+-3DaYvk|S{J|g4yk1C zphR8a%$x|R!-AS4ONMH!2!!&K`(0T~7$tl&G30!Huj~SwBSvXk#@%|9wsVvN?2*~} zrN0wyhyS7M^l9j0NMjPQ(7n!FY!PDgCs?X8X|yHKSK-CkRS7z->G@?`ArW5-mwT*M78B(87dHdqKf#u7F-E_T%hI z`jYYIyfR}%mq|rJ#&96>nt@?8cd>kWqc8Tx$8t>nhUoSA1$iGP@py(bf|dLC`j6re zj5mTsk@uz3o5U^QO2dFDdE7|s1xZ(LH=lB-&UVI7caLXlNaNZ4)FXzIY+jA2<)b)< zH-s;^=>(?t5It9y-<*H3fln%yABK0aX(L_qaYzUQWu=%jUVWT^&Dqv?W%V|_0L!(O zZNm}!BL;-wG~vMD9Ng~oW>xE$^3JdZz>SCBGsfl0aM+yiGIa@xk#J-%74tMQB|%%B zBk~kJgQJ+=5X|cyTxWI5{UA>Z(x}u(@#cxu*|-))!?)b)-G3tpe{r);mn{XJ&c}s? zI!mi7!4mAkX4tJWu<@dSIjv2GJt50{SjWr7h$07uFtP|x(R*#Tbt2Uc!^cYQZ47{i z&}#{hhWqqnd1}<}mhKu;;9tuc78_%dP@S*wkKPL9fACng0=cV1E;#8<}O8${%%v3f>J62dke>KqfQ3me-;t zJu}VP=KTYbFo-vdi5OnJXQXQ7$K+!oWZ4g)%%@69k@jD+fshjtM`Q|9y7IdnR#AD@ z-Ba4VHH#C0ckNXKoS_T_bDs)PO3;Z){K;F9DtKq4=94;!q)^fN)4%X(k)23t+rB(n zH-ptiVr4~&os;=);C1;M{#L$fC7&M9XmtnxDEN4&_xsgoLhy_1oSv~uIiNgjLK%`@C zMR1ptzaLsuvs5xN3Od^C{dRFJ{AhtIH#JHX;{lTony}K9_(h@Mx#)sS8GhtxaPHf; zZ2S8ks&aHZ0T=O|Pz`wXzQPPs1yaSlw>UlTfjg&EOm2WdD`mc%h z&T{+Dgf?(QzLR|YaT}gw>|L-Bv`;4tq3kVp$&0A8Z=dG4^V!kVc)uu`F2jfAV{Y*| z`0e+^^f)4Nu5lI$iQp;u+OrFGW|9aYw+=9Y@a+TR(G*rc0PX~=gapLE!YqlSRYhW$Ace{E*miqtC#r#p@o10pM8su*8@zLlXw$dzX9k z-and9ZwQrg=pYdy>D$B4zjS2cL+`V(B_2tt&FlT0OVA%e47GC*4VlnLYej#Yq54_X zVBWmBK%%{?8m(K*9-|>;R zcBJgH3;uQWMdLh`x3o#k2dXz@6@8ZNVWC5t#2TkTDyK?W4n65_K(gQG1H|Yf=1}{& z=1SMv>pQ=M$8kRQMXk86t~mOjFlA8Qeyi#dZhmXIN@p#~z8vf0@m4#g&d6W9~IV;!GoVk@pA4^-bd4aZ2_p_mf8{bDCZMCD^A5$s0Pz%|&wFGXyVR^47P{4aSh7b++Jte|n z&GQj~Agh1l_lpO-jx?c_2VM2vbId9wQ#Np&{#X6U9 z{_|?6KkIFWhJ}%sOn3=#C$5iG=H{w}x0y>e9I%y1qS`Xe#Kk1&bSt|ZZcU2HyjV!z??51V+FGbzBx+1(m9I^S%$2&D zs2mqo{n_NcRen4JBAoi(vsK9ep3o z%YUG|Hb@9L*yGcO2rlNqV>NVidj>={d$#G2d!Am@Tch?@2#!e_~q&0p#h z->iG(DCWE5m@ml0DxX}VOkhBq-h|jt_BUK2NIGcH46~2uf!isN*dSBsB(b=TKyZh! z3cS#H*vWop+}6MUbIEGScu}=4ZBYO6!|-#y(bs8~pya{2>b09Lo>(aiJ+aoGtl*qH z!eZNVA?LI!j-Ey8-S3s__zdZF$|ohXi~@$qUlU*4rHjU<9PF)&ALKJ_sR>_xA?ktk zx=^+p@KQj86Hz%Mdj#X#zAHDK=3f(0?Or#`M&lZx@LUR?x=X>;L zQ!YHwi1_R zt{VRXvZ*HiTmNtFo`RP-ZOl!(D4>s0HdH!EBe7TspDj6*6mfCLnx0Fp`6@FUKBhH_ zr7js}PAbA7zVS5GFNglb;+$GUKifo)u<)^(Kr`IWpX@1_u&uDmnkPtV33QVDVAdr& zAnCbq0Xr-Po?Ve_r)sVB06?$qyk>#o#FPk~NSfvqwiP{3?^FR_($H)h3y}dMSGu@u zwltw7QtiMUXV|>Pn(%8y@_kStw@>boH80TgU9xWS`PO|inlrZMeqfSB!*zun(wU%9&>Oawnt#2r79GqmEN&d>2LR9rWfHc>Q!R?Jv`MmHMTwFv=2s(3Vm$=Y zlw3)MDBFD*kZVFY?1|i-V&nxLDxIjPB<)GYB<72>tk%qpm0C%N}Z4{n5xF;>V;86Gg;AbK{kG=dDsG0;% zw7S;_EwSoyd7MWJdI-YX?KhpUoJ&=AWhZ12wrxh zy{XyI0d-k#pY%#>&gW{%s;w=8yh*1LfpE2MkD-uuuJiaV?_0=b9;yPy`!cZNvIvp< z&uXG4IDaVbM6X|Zjj_bL7E>T0Q7>7C{7V*{zi4kUY*sh>lKmpmFheD7)Xk9+tx>ui z0dAVU=rS>Vtf@77fLIQ_;8(n6u^e~46xkg5WyG*j3Aej&1PcTlpEqaMDlfXf=Yxve zb#_IzX=&F*dVE4YgWoN2@nlA(KAI8zvF2rM8fdIe_Yd{rAxO<4?46wCRJwap_Z|llA zVK#XYZJJW31?JQUEZnLkz-)sPLG|W0UX*W(xKZLz0)fA{Ae9D}LbVwE%DRdyyg|UW zrnve#rV<~Dbc(%9M^9fUk9&7Z!AGS~ML9Ge=w~F=fXCFlXSUcFq7rexp6?7~b4vf5s0Ls9+K>83IuhrJOx zyd<0fxaZX^jFN&?hJp_ctk`NDfIw2hm0wB7~N5?uC55_hR_d zaAe;nZ2@)rUh1g5yVY1+wZtT@5x$2Dhq440L4Da%xz@e)rRKe=-s{6Hr0C)Iz43Gq zMe1l?VS@iWU$L)>Cr_AbI#hPASQm#g#h|MdQUto@-!FEaG0aF|NZVoN_wS4XbsX!? zOao@e{0;szUY&W4wX2`~l%WKbi`SI-EhBWV&Cj^EB*1lRH+Q(v-3n4O7SFHnQCeHV zTMYSXUaH>8#fje18a?-dZ!DvQj_}7az>+yfqw)HLOJ!bu*ry6JodYQOT1B=$FBKVIq|~7JO@4K(xT_BKpQo7}gaQ zXC2`LW7uZ(Z}r;W(?2oAMy4=R4YYmMaD_kV_g9>9;wl1G@1%Dlc!MKPUI2CFRHNfK zvMb4H=uup}d}=JscCW;M$A|sL;J;$-VqfiMo9|l7RHAnCN5lWiD19h#zg_5w`@O3WraY*Rt(6NqCBW23l4Jk*iE z!{Ufj6(r<7zvoa(TQd{pa@cyH4AOACDs*DfUjBKiS*Ux?O{qn&YM1qq8Ky`>pCt{^wQvwcr&C^5c zna^ieY=+Neb_$Y^ezwe=S18(ClO;*o{cb)UrO>#sL?DJg!Uu)$RKW0jWsCB z#Td7vqBXP|!1g{(dZ1mscv_d02pR9N<(5>U${P!?uoRO5s?_u@~W}=$n`| zKX2gg$loQnG81izh+1oCBbtIh!Rhbxd|ZSN706EJDP_=tQ2AT49aE%bUV;HIA9d`o z7D$M~AHSF6V4j$J51iAC93^?>$^KfYT`A_kGm{G|&z3UEX6PM@_jRDtIWg@Ovn5tl zOgHMVN@@NuK|-{aP@zjwM75=5z{iv8fbO9b7fCg284@8TD{)cEDDE=5{>hg&AL$_w zRA%0>%}j$wOH&>&D1rNxjB)@!8Rc{1jkG8Y#JV5LwP^~zH!S3j&}E-5w%UOCQz0UY zcC(A7ts{U;W*F50gpx;wy2|~`5cOX5Cl3^SAEV+QkHLSxEzi~Yh$J_;5moOsV+UC1 zIlw|cdX(mrFj4V+dF_cn>^wFp)$SisW(@6r`?<#X5y^JSS*6i2Ur?|ZM2P<2G|ohf zX=3srq~tj-Y@78*@ zlNmbt`u0ttt>wdZ5FFmFA=QE>XjyWXZ$=7q3z_V9Xg%WUd?6g#4DM$D2J!J^UI4cG zIL|Y~i*80CQ>sk?#poC3;8=YFFgMH*%cC?e%*<;|{ffa@2V~I-=ZvSHm9-_SZ47r$ zvMo^!1hy3+t%J0!Z|{;))HhwaQ}&Pcw;x4df*9>6cD{mAj|oi?&=8nHWA{74-(`!l zsIY$H)DV2bi!70q$lT%}Gy%rw>Dxss*XK1Ar$hQRiO{!l3AOk5?uv>X&e6+H=mlo7 ziviJ>lwG7#0JN-@Kq#&pIA*SKsYUnzL5-P zQiCwZK4!3kIlKe);`7lI^!K#^_=#rNKKSvh>MgGH#|>aca!K!e?KmcC2(BE5qhnWf z!rATu+U9FhR+<6m##q|3CDI;~=X2Er4~Y6coAkA0<)k<7@K2mXli25>Ru1Vt;$<jt`AlRr8!&7PrTtt% z3D1q5+t77&jP++eYPhN{1b6J!`tk(C&|DsHE*Z%Y#+z(>j0LglRBt(Z#4;}dsNui< z!AOk>E8*!)KVy)!!QCNOkO@t$fX80Sbl+ivQ^7KCdG;;$3p^AJufyY;AVOBHK`&RR z2Wb7yEcNuI^StTn_s!KwgZsH8S7Pq9~0h1iOF z5y=8OOA$xn@tc8)i=InWSv%hY&Kw&)`TlTZIj0T|a{lCB2BCQgKNx68OCbl&M;9%oAvK@nuta-l)&Gj2@&i>i{x7GP`;OEn@!i_RPEA)qkW1 z<$3OR&4rLN!zx+a>43nP{t&GLv=ip2Zt~8OXC|ewMCIkkuu}ZkaT9q8SC|W8u4Y=2 zYDBrUusdvoU%dSSNDg9!hR;qXk38ZoJ=P zcqTbR1@<~Z>Ta8(MwW5gh)5;m%;29RbTgfNbwMyi?4h>eKBqQy{5i=O|*_CfAO;rP>>}moX8A!9<@F` z457@X$;U}ho4(6(_59O$zle~9Q18T<-+407s4wxIBq56L+T(yW_HzKBw1G)u&NU!h zmJ-!&f|AI~tF?XbOStdakX|v$?OvtGJVfk(SAcX*1aL{Eeb!{=8y|WgBl5Vc^kW^H z?~Q2dDFyU^h^&8}86>?4*pI$_Rc60HqDh|7+3LPyNneRgYf(l|_0BI$0{KyO4Zhtu zo9j-q7}Xfl#q?vmO1z2J;v;DZwf5h7#SUWQSx4F4&L4k19(2fs_~!yNtg8}KtTWYg zXB7h&5GZB#w;$@=%=RsiKE!JNnt#}R#><0)OMz7l7Pd+BaS)?U zj*r21Gb&4J<`peF*xaUG#1D(PxDZlu}+yFQvuULPP^>6~^9j z8LCt$vxLz)sl?iS*4;4P`TjuwLo@a6DArQp-uqt$Ar9B6J*{9Ru@s1q^Axz!@3og`jyf79h8ph`7oHX80D<;yC4pUuz5ZVY(OOPY*eH`mv;Ha#d!MH)IAd} ze86xdFy+2F_M9){{VzDKV|p%qbd{mFHVT*CpouKRIBM{B49gBud)Cy#>_@7V+Chkj8)`lWY4wdkcrdb zdHB(caRE5$F(hGo2q>I$S+LXxB_-&RW6Qr1{d;o(9r0B51y;nmILv= zBeMPk=JtKtoC&syDHT59j>R) zXc|4CP*^pqc_$XIo(TA71Gis{H70Wu@G;9=K z8@~+Zr)hVKHeGn27kIXR!Oq30jgS*46^1E{$bWHPA>|QKqz&RYy)c8M`-3q*tI|iM z&>(v{cFfSeDhC9%!HUx7rZ9c(h%X4!^a0vjA>pLTX1N`s{V?mKeihq9BF|QA9NhE6M=rmAhPIf%n#D11%KFrf$k+Sj~o}cnJ4^fd$n&|RlzVbch1=gDnOM&Aac=UN+`(lHcaYUUBA!|#i z`GO%DZp6tu_Ls9}I;n zfnqVq0UXIS*A8uSa(AW2_jDd{HTCE{B--mMh`FEPW3ZWy?s_~G6nLnzC(oc32Jahl z+;jHxEl4&DR(PDv6R40enMfR{TV@_Om@-Q(6@Faevm|2nGjg7yPc)5tqqm1Ehlh*t{1m#X)8cV$H5L^D$3X@=q(&TsE#(1s|r#gH{~mTT300^!Xw zwEX+`cBuXqEcLnnnb5Y%N!-~xITgTR%hCLlK@{4AMsxI1L)0(N{jpEvGiAZ^_*Xpa zsp-h%!C3ylITc$Gk zKZ@-##Gp*bRtx(x_}XB;kj%cjIxdml6wOh30Jcj2iAIo@TqsYYgERsE z7A+sF()i-vF~iqe_ng|lIK>`CUcp>_cV7lo@rvBKeO1sIid*(<+0>TynpTG7{unjC zp!r=xy|hI+kyC44@S?7x++qhQ{drPynzW}S%F&65)iQ9BOKf@P-JBe(P{CGBK97o} zb~_?T-vr%F=1?%Dng%@x27}yfOAl_A4U$4P%o__O9HS=MLYryG*CFPIqM|bZK*S=oo{9NMy*1K?cZqaEQ%S8i;5l98BwG$sA}DP5byz+e2Hio8&cWJ z51}_98Htv_n+#OrVyL#n7Q^j<&v8G@X$L2#{-SmFiqLLsF->dix)50iQuGUe3y(pG zuBJYk;EiWBlNeW}Ze0bw#!UZ(iRGrtwzsoTMrBo~A|gNx={48qCP`T-9#Y=E_>;mq zjs7dCE#xbsdX-i-=I0spnPOG)9>X3tV4N?xSt3$OAFZND9z%jbuMB;AncPQue|c|si-kIya+>U^KKT&tATrmz+@%m~}S|&5Z+L9d{_Od;oHTyU(joA+#PkyL2gKhH=^bW~}n^at`Dm3q3 z3TnFvhv9?wr;#OuJ*rY+eeN0NOiNuTjBGT5Ax`y8u<`jg8aD1&IbGVB%WKQcaq09@ zgod;u2%KhpSqV>f?P1JTU&DATV`*Or?=uSC{nyduR>zsoC@)8Mt8*ZQGn^4|-NmOx zy;pEioShsFqO2@%QAOHM{gQ>!_g9Qo@>W!mg|;a}p(-)e9-xy8*@+#pnG2&SUol5; z>;(_^k9eb>53q?B11-h*fOHO2{=*MGJalO@bx4~T9ziY*1FWd!uigM9^}G3GSZ+z> zwV7~`p*9R3;IB*KP=Z&8iNOa`kkNGd7&rM`#ZZen-28NH6XTcYV#}T_3%vb88mYG@ z7!a9e^Wz9*l<1Y^W5L#J+dH{5G~KzBYK-v(W&bC=0)H z-ye15r{)z-2ftSZ@CWO-7&V!tpe9$(Ig8aMrOxE&fWiPn z)mbi{gEu`_$*K_d;Rlhh-;M^VPb+MZSz%>kH(RfOw7(Sz>Q71I!OyeKykTtZ5)wQS z+>sqh^}+$C@wYFXattwa+>y138*(iI0elE`ra(wGlhu|l_}Qwwpt zB#GjhJw5BC^p5pf$oioJ;9b#QxfpV2{2(iW_u(4^yzN&qlOp$N`bQ7iqCtUMw1re~ z5~yUyhUaaT7FPsaRoN{#oJuW-HtGq?E51Nu?S4r^Nwmb++TJZOBtL@t=1>1=dOe)0 z28&L+c03ivK5GTJ#GOLA^z!ae2g${}TTtw^idWOc8WjCrFA1$He}F$F;k|WoTR$%k z6z6=sGW(G35o6N%HBFJm`;E+(G1Td-=M`g$B%F{DWZZ*)!r5iNC7UGqhd(ha=$QOJu{PgR6VU4dXNL$rZ&nZf_sgWY*T8lm&`Jf>7p22_&M;HK18CqKRxs zK2~5i%=ODYo#XJlb(GI|?bQqWY%%HONcORX%qTcO;VZr7Cq@N5D+2NorW`G6HNt@iXK%Ip$Bd(oD1S;* z8mM!jG*x_P*7tfk4s^)5=%P{@PG!p$r?tI}W`i>VnO~_Eb0y<=H~qz(9IN>7oCSUF z7J6msqc_g6C-%%h`yrim5e&Mb+@9JIH45nN3gk_r09wz=bGEPHr39&z#kZtTh~p@N zQ(G6|DIvNbVI;}`rq@kM#<<$79|E(CPsUIwFhk>ExGmHRZDs*z;gJ|8r1QP5C1XP1 z#h`yUmoCaNSR#e|PRhc$KPToWIP8fWr$>xCXn>-Hc_i)_j<}v&E}J}D=oceLc8p)4!}a%9X|vQSv@Xsu z2ZY=rg zbTjmpDZKlE0v)n5uQQ+j``Nh#QtwWte(1Hij0-Lrr)bATiHVp8Z;8N-T3;QmbPm+W zKoc|vky{3=Yc*+7)^!sCshG9Jk}n~c{AhKlEWCNMVX!wtv0a~??e?`D7Y($rf6nqT z)ppAb_pG_9`iula`)5GUYemRAk2cza;=8f4#t=G+zx*?TYJ_b?A(>qf8pPkk0j59v z9L+7bKcJMny13cB9kFq46ww%~k>6*ezV4m${CDIWN}S6#{*HRRWNn6UWExA7-M~@L zp$4rCZq%Jl=2t}87ub>cPJS|_?#|=9UBS0-2e%0G#b*qPm>X$4B7X}Gk4?Voyz(Ad z2A*~75)pC0eoqe|KL`-Dt%xa0xNe(p@d6p%_|em4LCA>JGx#DhvOHwAWHeQouC+}c zT*Z)oc~K~#BwGkd7WtR_1+B|6-w;x1of79LGET z$L&DEqJs{cO;Pc)@HSXh2wN{+q4SmHdT9?DqT4;_f4l~4N$_Z=6;Nsf7x9>jrl0w$ zBjXuBSCmN)46n$}aoCgY0@qpdDP!rKehM0_&}meVxgD}4E}e}7WYwzB(=N&8!4FT5 zltl_|g5~=a77PW0?Fw;vyiWA8h5xh3cH!kKmlCm#Xrj(m5P>3Bo_s68lja4@FcJmd z3V9_|;13jv?>n~Zz=9Y#_3tM8COuZQB?_EiI@+)@U=xUH4lH;dI1`p0lnrA-Fp~iJ`h1tJy&&|wzBFLddbEzbW zL`x)>r&K3Z7BpNvG52{BeNChK&)ulBnXtp>sCmb8Cc3viJ-xI98b@W-PLJvaIzj*J zW#KH-&N|Ted~LcV(DdbfCuLM|vVW^IENMQ@{Qesr+$SR{8dHbsgZT=Hl&&Gnn6^y1=DdCt< z!9sKNlPyJ9Odz?HcbvKUH6U#XcH{$Di;mg?Hl3OZh1bmWAJzUGF4oA3Saf(z{wQa$NaR3Q3oQX7 zw51TH+d9Y%?JMD$@W>WK+U7!U9_SFEau>Ya%i)_W=nGH8TwN0KRr6WYl?Dc3>Cl56 z)^5SDP$;}0)nGw~K`CrwXX`t2J zRJR*6M)8$J8LKKI@k>pVZ-~9@oHBipOoXz4UeGqNoD}hSzwel6Mcsc*?3LNx_~A{9 ziMskWQxaTGX<~=|Q3?e)i)tYYA9~`U%hMbeF0)XlO$=i9Hfnd5X(oAuQv^DpgLZ~S zF~mB#8g*__b#7h2_U#3L9xA>iyGH?t4$@J8<%lgC-*(1bj|^8hn{)y`!TX&~Cd{g*LC-0%Q}04NiVN7~s+Rrz;rH#K&jsa=Vfjpwj9})6Mmi|x4LA@k@hKH9=R_!K52)amKsRIBEq13>6TrNOoj6DaT-FFKManj}QXZl!)X5&HwK`K{}Wxf&>p z{@{7;F9@B}LjPP9{P~M|z3;|5R3rhsqeXWvIqTJV=>PsC>~Y~85c2>1)$isYYJ*z5 zF*HLV_5h~UAqV}hgA9)NjkAXzfmYRwe6cKte-AW@O(ygiH$n`&&PDU4@=H044qBLN ztmpvwF<3+OdF`a~jai$(Z&^ODmrOoY;Pnuy{mt_Z@U49L=PAl6KGpJc>_k8Jx6eDE zq}JbwS-jzGBTLx)w5V+Wx)isXLHn{JIUUD!?Nm;&)5o7ho6I?wi!-eMjU`hlDIh@z z(t*KIz4AB3HKMTQs+H=$dCK=u>m9-BWtAc+gw2uuW?rJgwpMEz1uy7ALXTG zugriBT$UpcfGoY{GYfAw3-AN2IC8{F88ABLP`&G}^=l#$3_wV!RoX&M!kL&Tc-*4;wO+=T@OnpJ8B=yDWVBpBo`Ae$M<4_WSeQhZE4xRCc4n?9HX0oO~H_$9v*U0@l_REQ z57G6Z6`mjXG1UD0Y`tiTy|RMWXjAMRK;X5EtQI3Ey`L?5zh>OF_)twozt_tpG8w$S zwr4`wM%q)dIsR3W#@1;_tPvR@wjr>S{3&IbK8bGTyf~FSBG#yTEJ;I#B!akP?R;)| zgE7+BLgyNge^0g&pGT=G_Md#Q%&T29uFRDj1j>ZFWDi2oSFl1C5=mVX9yQ;*MtlLkO>F{4?@jK# z$6D2_;@DAORvpATcgCu0gDKdpe55E>nP_9XW$G-gRou$1u}Nfl)S+E%?>lh0(tj#a zS_C6i@s&|30&+viKi(du*d-xOnoWE;vS+&8UmW=Vx_&(F5N}XWu}uiz+{_Yo zki~GOyEwUNdC_}=eh`$GUP~QfhKNFE#+(eZ5gU)V3n@YqYxF46YTht^Ky`xG-ukC_$FGR zI0Q;@S0A~vFULA-;L%0CyRLlb)LZ`jNTdaESEzx=5a8c5<_4a<#+ZlfWe$d2e^L=Rf^S!c6F9Sv*dEnaSHfg)D)BEaMJXaW&M8l&f>5OOsaWGa5Qieyvg>; z36DHg2pe+FI`0j-6hEtQkdFm;JoT{xKU_o1e;3js_eoI#9T^4x64jHF1Iu^dwBszO zdL)G0d?+!b7t1}zMZbXN1CRy-^%pyRNwJj>1mI1T9Fc6=U9hxHBI4KyZGPfAx%!Bw z13zjLxzJkECn3f{lu&`VDz!}Og*3i_9X~G8*lQTIovhKY4YkLg(feZ>R8NZc?aT*C zLqQK=2`17!8p$BVE8Ze$TWeX|e`5BND^ zQ^^i$F3}omNxZ&o=Bz4$nn()5nbudGsvA3)cL!Y<$!Xb2Qkkh}48qgEVt*rE=4mV9 z{K~$%wLTduob^-hRU?^tg z_#edMM(Lfrz;#&X{8sgc^!gF-)QuCAv2h%%QRXvC4ItwU7K#u3c^t+P5?UFc3f=!YI@UtSI32N;BL{yQ3UWY z|9i{TP4U5LLNuTM&hTt@+6xMvjK(aQQUt&wC?UI+bak@;JN!u|iV0=fIofM~txXj5 z3pSo~7oWhT>Evp6z5kt2hs0uhJTBdyPfuF?p>{5hN&b(f|fRScD>keLX|=tHN^J28Zi7L>3)D z3VS5tf`6Zf8|=jCyvJ<7Nf>T-@IGvoT}!hA&oFlUUR@zn$C*Hg2kQX5Gc}azVsu6d z1mwfp!Zcg7mXp%)cZc->yHP^lKVF?4u*cTHzlejzTF)+ws{MB7rV0E=B%7+x^c|CU zHnHcVII4aPwLaZla-AITpQ%qhkH8Ia9w!c-%sh;4SVa_Q3Y|BVcsM$E^YyrwV`ZK% zS(ZdELyW*pzKb>W(#M~4@Ow4kKZs#lQ$&qTfD0HOdX)D2C>Ar0WFd{cyl;La{*vL0a3s$AT>9?-*DJu-?;}0Nd2@9X$B?*dWjuC zM-{3bxVeh-NH(p_@jUGqUdzitXbFsMMn%gww$z>c~ zf&TE*f)&P6FO+=~GE7`n`p)fJ&+W=q`N+CUHkZmc4kw2KVULc=t9;WD6J{16d*k|c zc!6yQ5qM#}%p+z8KdG;eUq=!tAaes@o%EJL_>gplStB5yf|Na z@|B-OmVgIA3vZQL3zdPm9;JeC1edm;_3fej z{AP%ho4};2zu>Kw zF0v}Kvrs?dC&%%VWpi@*Pvi9k$OCJAINF9tI_dqPah1xbIQd@YfrlW5I+UrlFh29J zOL5mpaAL;wil*nT>8roAO$U8z5R~of!6h${D5k|YiVfBPytb~K`b1pQt*WqxC~Z@S z*${ri8%Jl9-HEDSyad@baYYctBzh4XAX?77OJ8K$xHM}iBV*bxJVzQbFN;4g_}0!W z5XQ2v&ODWKA3HV<`zt{Oasc;}@YE?_L5&D0AdZcl$etI#%Kze%QlLKFrCkwzgEdyG zLK?LWUWpF>savNIQyB&?%EcnvbE-~+TBke#%1_1qu_8j%DTVXB@3Mmy+wuuodYOV? zQp=FF#I}ahv$)<4k8vDman!nqcRKJWww8Xk7Dp!}6mFg38e=XAbtNLyubgubu1Ah^ zrGuyCDi}s9JqRu+N0-Iql)$Jcw$ zh8DONes4KpNgdyNj(aT6%pm>0PVBnTO+a!mF}1DLDVz97UNmatYwYKU7xp3R6H)6k zgh{oQgMAl7XQF5o)m_^rpAZ)lQ2>3EM1(Ym%=$Os6#m}%nAh%fliKx+eR02pjmyqn z7dt#&P|xr^6`s@^#^#pls&$)UbIr1Z@AU7PZm}T=Caw|V=EjBoFP1M+j`cV2iVgR~w<@(VB$%&|SozC+=+RR;TY)?CjL8!?cCG6O2iepW z&~?iUy#aeri}d%NbCE(xn?F6SawZkqTGnjv>B->r7zf$q3?A+C3*IKS_>I&pzQtxV zJUsHy{qQdkQ#1)21SBmYIcbRWWY@@iz&o}V(S9D8Qyqq^Axl3>@0G_5B-vtNtE%9* zLa#CN^Owevzec>6XjrG%5!>@Ltl;^SAD*|XP#}zK$BjBRuP5t%5hjFpcJEd906a-l z(Vy_w*JuA7r2C~vXa=?0QB?9;vc2Cleg=d6&R>QX04^nTvo-w3Z5-@tf1&Yr72{O z?I`PFa?9xDg;-lQtbYRS1lBwsS!2n8;}a;7RYGk$f3q<+P<@AnBb(PWt-_AW7S12M z5~W|t2==Wa#J)ud!L>4@NH$yhuCB*F$~31_yb4h5j2R=PDn`Ap4TqT6##y}8} z!J{V2*2yH6-AelnpE>YV{1R?wXl1yjAK~hw=x~!R{Bo9^jpC6qdy$$kvDZTP^Fe1$ z5khNqD2<1&Gn6>a%8Og@+Cs|O!e(}^P7Hh8&h3_GuOH*srSJD&H(6@D!J5~P$E8{l zlt<^85`{|m-DJR%_St2x_`bx0i4_@Uht_pkZa$ZSyAfmZ_ij9{)imG`Nue`p9SS!f z@;^L$5i1}`U2a2EnXRJH7)OH$EF_bR;%U(As*m^u1aHfBc$w2XInB`fom0IuO&kot zrtQ?7JRZYC{ju$dSM@YoBzi-PF17jL(HEbjfbHNqR8t?qwacXx@eEC>~xUNW~TXK3M;?)r% zUaXFP8M1|yNI(5GE%&zFLew}9J_hybifWp;*30=mCoI%J-$dD)xVc4UaHCqS`!NLo z7(~s819ZL*5}w=;8tQ@OaGe;F-BEPG970NV_?wLHDK3nL zFJAKe_>6%1uj{$}R_rf}hZ@3&{IdPV`Ojv^in2CAex?lugxvr8LJpd2LiiI+`1T-@ zxd~V#dzANn1M|f~WTHbVA7=EHds^GqV%Pok)=a{Ef(8OwVSeS2v5vnl2~manPJ(uAMmzX$dT zN9`3_$#mE{?9~XELdFi31}C!@`8e^kQHV9u6D5vhTK4@_C;oR2n3=+gIdG*;N1VP& zKqSrn{m(0WU#J`h-pZEn#QyQwG$d$Z9mC@id+aJBnfIdBAyl;!vJZGSZmHU<^VLUO`Qnyg_*_WpUjrH9e&>ZQK1LA@ z;WI01XXglE1|*!BT%OPh#9K-=i(FkBVPsN6qAk7vUDS!d>7bv|%^Mp+Qt*JGim|>W zpzN6ofo_kiZrt%4fa+3|euzAJ1@v3SY2P)r*Sjp4ZcSO0Sn7tTK>^VLCZA5x40|E^ znQMoBpVqF_j-mThY9TEl_WGs&KITc-Fbf+L-V96J%@#_ZgWAZj-k7<@0q|DKd7N(Q zXgY!R5hpz-5!7?^RC63_pVas65*H*(cwKGH0)Gol-N}1^CdXh)T@TlH1j(EHEGYkE zS$JepOTTqof4|jL)eJHwlZ8g~Lpm4duhp&x4cu ztRC-=qflVgFk$_I@Bg#V385`vTe54OVv(tNh2+lV>u9mXM0)gAz1fCd zqv>fKFi(GDR8wLu*Fx2D=lqu{Ee7n>ybnuARsp@O+U4)A=9fycgFji@W_@-DJWQ}n zwGN3e$?aeK3W;M>M~a?#<~m=`1#GK@{vJ^$WvhV}RkrosDz|vVQDU`l=vT-7qQgd% zx~VpZ8$ZydM9CIHfAo5i(G`tmN~ZF2{BH829Qz8v*GR_W z!z(|J)iR*Hp&n#u3&qZxmN#AxlYNoRQ45Ttq4kwXe%G*U7A)4fXMGL{lktj*;c#!R z31o6%w+NSgCkFLe+6GClL)c(H*H=8Y6*BXrs5j0*n3Z?Q=q#a!;U}71lvpyS77z{h z&s7iwgwSiN+r_Yft7sANCO7I`^vR{4H?Lyim3q7gMpy%&i#nfN z_-X>k(ztJ(Gz+&h&O3s2k7~EcEmdnKgX~&NNrVAK{nc|oyR}4!sEc7Z3M(lEyQ=AU zL*bm*w%A=~XcKY_M{u}uxAQa*KC{#g|4GLv5BUQ1^JdeLpF~8GY^gp{xM45=Y1g@2 z`~s?99P4BY9lJVFxQYY@LdRQHM)j9ek)+R>paOyxK zZe^u8E9Lho*bNO(aC;;|i!u;9Y{lQ&?&MK7L1)eIeazzAZU$(QqR3`G1ri7bJUQ^; z4ZmSqh}Y$tz_}5+8gF{MkZ_W<>jkjgZ$ChP{##HDHA`vrvK7)7CCy9a=#TEpQzqZ_ zGp~4xWIqlz{_1;lMwnR?gGpi)U>}E+1tmmE3Js+KWIm?9)I3Ba!G@wSMU3{7KzOPV zU6+;w{6tQLu=EzSVWiV>a5zzDC<3R9Z3S_S$p1j-B_G)P|aNg z!pW!q(*n5YM9r&A?(W)x)|lG*jtz?)>wg}&+AF+};_pwvay2%Fi_<+|?pox)GImkx zM(_Ku_(VOH^_}hSHT#ZYx-#^k8Y)AGeL_SKd9Z}&k&0&#JWfW+T;V(oQ713Y^jfs; zh})fhuIvlB6o$il2SFrAu*TJH|mDQJ^o3)8sL=i>&ED!yj7H(Uo zq`*(OH*(8nK9t56>K%s!nSHPkB`(|*A2V^-iAkx9CP1LBjJ(+2;*TlI9>Pfd49yow z>^NMV4YivD0xf&+<#o02Ix}H)t_P9SPo#IqUd=pt_@*MDaLSx$_eco0z}M>85cS5( z-W{6E;;F3o?7fe=%bEVD7mH8Esdr-3CSP^Gj=i05<1?0ckRx&*GN~ZG`fo!kPl0y^ z0sTazndwtyx$Aa|V1K=&(T%P#S&t~C%44ylV}5(gh1U5B(cYRu`?!ZzU|4yBw7z0B9l zMSq0JV3SF^mBSSs$uEyerp()n)C#?(Bk4!KY!)Uaa5^1M5Z8Hii;g2H6vxr*RSSB< zWOG$4`?mXRlQ6NP7N`b;9PF>%9y?VGX-BbXqFgJ5QiF*+JDnqVqH$7nu5~P=rKBzG zN13<%?B=J3m$l&&B0k;zaW=i$*--GYK9T)9K4?@PCY?{Ii0pmLL*FV-6D2wwmxN$Vo3Vq6^AaBuA&VZ#$XGA3LH$XAg=oHs0THmn+ z+V7t%%M;UJv0twso92yLN!Q^Pu-FGg3;v=#;i0R{@@>1s5z~sv?Lz~!jrvVT8449@ zzQJaR3)I!*oArL$Ie$e@b+aII-McQ>!4e~FI5kct@rUg2a@w*gw+GF58GVd0dg0Cm z8i$mV?R?|li|{Y9kMrUvF~ERwjMl%ugD8azF&uc@DTBn(J6P&&DC>w^wdK@n(TPEL zgBlwx_FoC_o=BjZk;-e7&*x{Qka-2XI1;^iAETnNVHJV9;NIeP7AflD$#w`)i9`x> zg^g+hgu0MeyP~{-rJkr~58SBI5c;j7o4bZHM_UHDlpnOgKyI+L3G=_);QyH1_vUAh zz*q-e_;5YjEk%1g`4GlRPU3~!XMt0K{~@m%!ufy_1L-76Y*w1=bJ5Lp#G5x4I!2ch z`DQPpZSL&Jn-&o-vUv63UP{27F5khc9}LCY7T$pb!zA`~)L$0-3%_u1x{!GpawR>< zpF^b3^f955V)V+V>HEj+ak#S|J#}Wmvp~Q5TD*YL#q4#+`YlR-ZEbtP?IMgO*magh z_}*ZL5NU-CYFY#AflC#C1_XxMUrOW{)HecVFM*O6_AfpRR7DKjIP$g96+?Us8Lf$@ zX>6@&GjmxA>WHdhMfg6ShZ67B{;MUQ7Fzf`no3i&6(Pz|$RQ5gy_ryP+(ZN_1G_Nj z@M>zneyq8q@^It7TUEgubjvr%I5V-VC)6)MBY+l!P+MEAqo$Fu<+}K>vet@{n z*08QR6a=M3D4%9qD1GHAJ0@H}!LwUE&+lU5-Awa-51l;ilC2g%aW*8V^Q zR-z*yye;jA`Ebn@q?Cjt;bgS;scf&P%_|6WjDH53`weh%7^Yp8EFJP2OLT|g%W^G| zmj$%>zc#^9{^sG8p*>D|PuL zXyaNoLC&SI#h@Xy^lpqZ7+nHu=ir!49$G&+_%%;E+K&EgYVWbfXWO1z8Qv9o7JL=p z(!+qi9R{>=!k_${oM827Qh+cmapaD{#1&cc75~LHanb4#;Ep)R#H&`k zJK&bN4=f8Rt(*N2*-6dB_f2%{%|oje#$Ay@Nvxz5{ISI}M8NW&Pl$X{2orM9qgRJN zl8Cf;2s+LT`SaUwEj|rS)Ht*Xa7VpEpdgNJmw9GFuqGTN9Fii3Ul%vKcJ!maSmET~ z6huy_Notw~L9q5~@#f=vB?uba3Wg9f)MpT?-2*XN~fHjZ4B8nL694KgNc5v4(caNS?gwG~Io^V2!a2z&^| z6@Hu#>6yaWZdoJ{>>%$0oPhdoC()Te)84OBDs4+ih`)~mP9V#0<9#-ms-)16^M71! zu%W(GI7QgKBJq;%lkdT)!0EQrO|W^8x$Om27ycb%f_2?)NJdNg^Y#y&J@}V(SDb#9|}6^3GrH@64p_*KGX?w4Kn)XRp&_B?wnj1FHQdLX{i;4Ej&) z@%eGTxG2dDr1jVJ2T_@>6p( z{G{}+-pSZvMv;bi%CkFIf-GD(v)}xqZ|{L&i`n){h_(hqr76&n^YXshagO!vo@cN4 zu#YuKO^soXGc!XP?qM%1awuue3*k-CS~B{u6Lo_0u30saBf-+-v~4Md%8${ig2yx| zm@V{-ql;sm2h;`?)VG=#l@oCv3$c}f(2^X68%ocmel&r8P<*xK7(12;RH4MIUW_kM_C(VOg@>{t8)A&cQeWPu#7~Gv%m}` zGdcFS$xywi;^^Jv3+|WxF7fkYPLT?kK`b18pUaL4r+cZhki$iof2qZN zg`{NS!WQ)_IPdmxpqByZ1!2RRJ%cO_mt%gIrG-+?x1xxrh7l^zFDNIA4K(^@0y}zq zervtOjyQvTY=PaT>4As{(*q7>B>@RG9K~Q-ye`q-ywpkpiNJ4rNxBUc$GjqzB}X)6 zYk^-u@MvN*l?aH!4^lz9h`fC#@a1MjJ$G+u90qYRn7!Fuye$<|ssrsqPqf_i^GqM(#1tDbI2M0%FgHeji zINBd5KoONa^gD}^L{3V-r_rpKuwA)3vz;TInZ1Z#l=q;uz8ctwZ)2L%r;_guCycEo z9lP{E#Nu{^7&hXe0E3HB>z=WgMN@eV3@8Xf*#zWbgh$M99HyoDiuxc6sO6>}A9+l9 ze=6iy*YP^xAf_+jO|+X~?^=+NPEEuw#{`$|0qHdgD^@eLm98?9US;=AORWu(ke?cM zLdb;0X^^u=H#6%(vOD_Qw&grYd{6_utIpGkG`JM-HY=ubXiewTf6juM%g(y=4||&PgrF3X?)X3K4oGntf1*j<_jd9&d6B#!!ft7B5BlvC zKCQ*``EQ=uYuA^6qUglioY!ja!Nw(_qbo0>K!;GShA$?_?{qNKDN4BVcFvf%VP<4E z?e?fO>M3BAa_9yoDG@zyRx$C{G!d5MQRyu>MpY(O#!q9JCo7RVDEZ$V%*E(T{T(C5 z1y!O7ZG56q(0p+I3S^!#X06tFxx}y=;0W2BA?mN|rFy(L{o67o2-J9Rcl; zyv# zNG@^qkqhI!R%zY%VOP=lu}c#BMk<&av{f7wnMY-WvJwwD8RXAm^={g~0XAG(;Ovw| z!G3QS&gh9|D8#_|6TA4mQ32xMGe=u;o`fA328}LP_y|WjW(Ir}ytoIm22zh)gVz zfsSC@!K5c>i*Ym1O2JrkH#v|Xf1C%1`cVIJa=LC|9?L5^y1gK?H;guh#yhMWc z!_m)lDW-v&Pt;03e|z1t-5PZ(bbQPayM5a2ikn!l!~u4Lsu%9{8uNoemNqzyBjl<1$QeoPWlpZsFHlau~8S zA70M$MQEZc$nLd=-#5SrmaWy}M16|P)9HL_+GPmdaWc1S@T_!!Fsb-`9WbRv#5!{< zcIQOH4{sLN zVZKzufP}r;-K``aIqeCYyyY-WAxcX>S?OA_7!Qk(S@W`H1Ve0tzNT`jSKzCN6OH^7 zO*MR7lGUd>f-~$0Oa4eSd43Cc&lZAFpG~>F$}e~Y4W~J~1QE2hYx2ud(&HP`H0*;H z@3{BD_<`hhy_{Wt&Vfdb5L7r<3)=vPYOuDy_ye~3;s(2i;uhyhfFqRO>R@SbUXaYq z4BRuVnf%%>`rJFgmUxvWfv+IDR@s3mt51D?^ZVqmnFD|C*V4)UzvCC$u8X((RSaqE zQkJD2>^1gdj1C-bb9Wd+7f)EK=)isL4eB?maT9uoqYS#JzE}2 z@5!uFZ|3-y9DQEd-L`r+SxcVXwS#Ce?&h^eMBJkD79>s^s{~IB(DIN5D$(V$EmQO&fN1fI=^;2V>V$ytq49kg7^)H&t zB1sjUm~)we+4k6@zfxds${Qb|kbPHOL4(S8Z0V1s4fy_R1I(!Uc`83-@W|_D9kDs@ zCoSN&xb*M!-tv|H8|RD6xQrpuI**AkpPR%^zwXh9f{_Of^ropkEzJd~1ypCzD;&78 zFEkcD7xvg!-xJVD&b2Uh#tyyjjpEBZ^<(I8P&;J#jUF;Ur!>C*yh3Esx@d07)U0DZ zXyHC~^kJfVJVuC=bLhAVqeTcOTNvRObV=CfA zq&SX<-)S9wei-VjN$x&%NV;nC`K9_!`27ek4O;<&7qis4lXeZV#e^N75AxFa4O^PH zM(x~$$9QlZG~qcSoO3%{_$NJYDqa}h-J$EPEi1eCXq8UPDEv#J!TJUVrVjGIympG0 zeb=fi1J;LkRHvLN=X=GRij&8XX+-%|WH4naP+bOZXk1%6;oMh<`dzuKApm#A<|4tg zHJr;dsZkP<#&ck(P4?8_5b#t+?sHOYY$=}6j!%EF z)Tz@hQeisQNU}(mBO*h7dRn9Bmrk?|h0-4!t0ZrVMw?Q_0XPq=Y=7oECnG*H(MP8> zEpDW8p~4|<`}1H{=wWO7Z!foaS%*!roD|Ur2;xqwLCJ(-beb`kggCTN_#|GFQ3*7e zl9G}_n0|U4&mo~WCpl(|f*AC*aQQL@t^zN*{I^#_PFW7w-}pLEUH!nr_vMtoAK@t+ z8XlkS2M((8T|7@-+svqKNr`kzS2eUNGT-Fn43M37ZBtm=>vb)BP1^{9p4?qR?W!Cq zSUKlVq_Jb3%dVkYDMu+4evXE88YlTA&$HQGrLjr7IiXQa6W8oGbhv5LfeFWe$TvTT zjS014fBPwf$Ju=Tpc$cGcK(E^Rd{J|=u;;yG)&Rr(UvbWpRN#EleQ!GL;8R`i(V#X zFEKj^22H+qmgS(Y<}pnPX8tJ4g1tLmPq>>UmeLv(Iv-Ti-wzqjims+lAiBbmi*EEb zoh{M6KzVsm=b&{87Hcs2P~>Ko4hxo>&)KqWY5INR?9tQchCeaSoK6fMTLsC>(K7lE z4^No|I;38<%yFrI)w$ERu0lKW5;v}5p;Al}-4OCd(Eg>Te}VEj(0r9n3Q33V$&irt zsM+!llhr)r`GDEjOCXzNlH-GMPnm3<2R12bza^`|(m}nwk)gO^0gMi%l=0MnE>=XB zf)V*WY7oWMMAwkGyn~dP-Ccl;vh)=*ePhu;6riOMQ6x6Hs{sVnpkyAc*zGp=uGN@TPDtFcIMzrQI*-MQ4>d_qv^<}UJj$kW)X*pQr5$*X z9r~-EAl_^g`pmpk1P}In1L*uZrS(u{APR6Q*UcWaWkfkSFinf$cT^H_Oqh1|^3#qH z8ywtH^9FhpOVQ5;Zm8Pz$(wt3>_I0~u&pcI#t{=jO|q5WTb66b;he3Af(Zl1_pbTg z{>E&~{{tJY?M-jrQL*SWQePE)?kK#4Wd~*4Yfd|TbJ|I4`dyh$ax~~)&8|t>_xoB= zx)ud1qD{f*oYTeK%Z5=y$E|`|c>a1zc z#I4xJ5v^sh_2zI=B_u@2TXo^sh*E4nbnrtkP^9ZJ z>&?`q;s-`LxxGf0C7c{s%djgKzyxt?c1$l)==B+&D#GI;>fM%OSy;l@!8e$1aMQ1! zvVc$&OamT=djAH8*43j3ex2qtXDu_h{5_(>%dI{!o>w@MyZ?1~{m-osLMn^#_Kmp( z!p7Emh`{pd)qh{K{eNGiLwUqNrAXu7y;l7F2zzhZuA~TO5Jl-9DyT^^lQPC)!}{?w zdJ$jNPZ79QCgce>ikIcPAWt5iItBiJjgYxFMno>~Fs>J(Qd)u@8G4XQOfap^<5GuD z#qZdC7MT#&E=G_x%{Y9uiJcZK){)k-Dqf_jTDAU4G9y?>@Q2jzkEL$eID_S%ZLZnt z(^FJV(~8taDD^J&9Y10n4AgNt_fxBt?4Y`-Ghr5VaQ$&{6io4+snfmWN_s`Y# zwpp0vT=~FhnVjBmxD`mVy|1osE)Wuo#0y(yKzcf%ZI)_Jh`+S@X|5>jRmRDXZw5Ex zhC)JLgKD7X0_E1bVt6+BO^oq4z zF!TBTt6{)sgnydxhX8r`>1?E5+v~v8w*9C26lcfUm;AdO;9ma3lXszzfsg$#`0g8| z5KkcJaeIB2gb6J)w?gv}xs)27ac?}|Zal!_#K{0G6$~_`$)|1$Ie>om!9fD|W58eD zEp8q$BPEBL&#-+MfO)h^-s7!5vDn8rWtF>c!?6Yze~1Xi#jeEmPv&xzvk4QM{bprB zg!>5e^VZhgAAg#GdBO|0dr#wZ=}J0+h~S(BX(47~jd^|M`*ExlGn!tQh!9OKochHy zW4x%Mkr^e5b@!;O@JYXs$XO7`l{VZw+eB#3uQ-y4>HJIxRfPb=8S0JiE$2IsUctUX z(rr0P{ZxKDYY#Rq!YR2xRnbPdj6m=>2bzjMYUdd9Uh!L3l$6%Nx%M+)O2i9h0)NHh zhHxAWbFtjGvwF1J(*P<75mm}NUE`Q+?m@*i6!+dD^_o_gRl44XpPYoOv4C;})Wf$y zW&UL=@>6_x1VOSe_r&P$(LU(Pk8@p)YkGWfps(zi#_#t@-a(cp9HcQ7=JO}=b4NhL z;I6wBgfd>)QHC#B;gVht`eDcEP|R$`d#cB7o-e&ru;o=4#<1~A;KpOv5!YW#Cq6%JSe<`$36@d z;fQwKU}oGyi-3U9XD~6b(V~un1w)TIdfZ|I^&F1l^j<`yE}0|VlV|u?W8sz?|Jds! z9+bp@TS+oYSd~_;BZkU34oyBd@tMLS%ss!eqQYW4^Bcc8!znC}K2YQDBNXdwcrk=} zUEK7R4n;ovQ5kPEXxiEUTj(U5H5dA#w+kp-ni3 zr_E&8TUE6j3hPn})P5R_MmKDA1Io|Wi;R3jJrj9E0S5b2BxCFtlQ41rskdcSPY?~k z$fD(*%c@oS7Pfb^iOeX&ClB5!aWjtcZY=+p@?0OQxBxuNm9IMJl+xQe=msUuF4T*A zSulgnjlEY~@wE^S~i7$ws(BKD$Oy+VX2bx6et-rxFjq`mwvMB-N^ z4mL^oG+989c`$ubMGV47yznR!kC9~*G!Q}=eoB%irqymOb!+8v;9#R+Y6-=JEn&uC zyx*gbB-5K;3N6t22Kn^kz-)K}+x^&h;iE1-nO=lBW#Wq%Cxlhfi4W9E$8dEw{!a^F zXQ%h{(l-v?2g&zYV6iS%_rR6RW+U?`-Gqe88S6h#Z|J!`2^0Yt}k*4$-+A#3$)gM&f8I!i@%3-~mLrnZ0o z=z|S=L&-f2$LC?ikKlg(;%;Q5IjSVjq&4^PZqiq<6Ldb{dKqGxQc(s)YRl-(z&9pX ztc3N#`^hF=Hx!A5`|dSpAdS+V5q_jLVMQ#73j5?tYOMU*1zx^q*uEI!yE;4YQ>l-L z%uPDS!t9mVIzap#u(Of5n$Um8)2s34FA#x0c{z5G5oK6y789!T`laPDEp_Eg?6E3& z65DVSiZ7bINiW2AcN2ww0I* z%Q=E2aD?S^;2zB5E7n2Q{6*}rURkl`wCk<$hSG5eDrX`c*w|a{SHDYMz zVK;{TZVwJRdL%=)ziQWd4c`8|fhUf@%=P;+!$M>QhJIBr6@yu2K?67@M6A7k8#YO! zK9B>Po88;(@oH`hvSH=WDHm_!P;D1X-z=Q9KhRcM10z6%AG+GLQkx0Jy#s*{e`=^}e$s`dFPt{-D=phcg?9KOePFZbx^i<+BKf1ybW;JP zLzl9sK=)lAjzN2$OJ&_ve%N7d-S5XgsJftHZ#Mlo7`L%D zs(70>2}jn2+T6&i=fETgZlNCfDDk&MY1Rz&(}}yC;w+HiDCldE{hm6cFgs__|C;A+ zZMPkjQX)h|TbAy>>y@z+{kvE1wviLp9;wF!$VHFY2qdg7oN;49k!Whr)zrEa3s5Ph z*l8EOJ%|4LD zQ&CO>((tyOVm6pdo&ni-!B4e{V>GFy)DK)o}3dZ+HEl9~IM_EV-C{elGU! zkC?bz^(hNi49VmWI0E`%Y-Hf)JBRs> z$6HC>$s7Q+{FuSRK;Vv^!ua#nD8sM@;cLpyH%ZqgFTcyxT=76M28zO#tDtyLt2*PK zw_^&Jz8^3=TpFFy)+GKY%{hh6@9BX#EFnCUb}c73w$H8=Ch^T8Mm+uc!!JsJD|u1G zzN&Rl_`@&D?I@v0VCsAzFvFmB_1^}hphCJrmX1Z1TJv=&n{Pcwmy(O!iiotco#WR- z*EY5nTJ093dAf$jUi zJt>YStK+`~xB_O!4!zLQc=?+L4P=of*5Dm|{BK69=q>O94ZtQkqnEI21jt{H$mDPt zI|AL;t(Uh?WzF`xqT&kF$6rnU(My#=is%x(|g1%I|(w+ z$xFUq@p}dc-Pbvo`FbcDNel_LFX1<_s0GIVDa6`;Ju^tpWAB(2ibOmL9${v%?DhZy zZMjopR1eROIA<%Aq=~clxju%RL9+hK_3yRRp->m@FNhe zx*T>=2-cZDGA(ODByO^`VL2tsT)6V$RMTm>u;7E!#2e$*owtX(cj>ap8~i>Zd365} zK@v&$yI*`9roeW3M{bC7{RmwCJ^^z0Gp--1?*=IRh|5RO9heF`nxsX-0bG%R7i&fv|HGvesHIOyk!d|IlA&jL5SnnUmd{VXk zT@X0P2fEhf$3g4f8-ta%D|=uJ+u1jo1?>1VmOS7Z2E19+z7+srjQUy zQSBYZD_%ad%gQjLi3fG40}7vNc3JP&dwescc6<{jk)i*tN|F&I^wZ|kR=E60#4@~r zq~H`ne@iA85M}4AwwuwKdQke*5F)VNB<#_B_ z&WxBvFzhs8()@P5?@@{ML3Yj<*)iQpw;3{~%&tk$XFFu$ z=P4uX-E_?}kdU0nq8^T{_vJlNTtN~Qu_ z{8%AkgV`t6@^y&z24Xbr;SH`Dgv#BSd%xi~;wuz3Fm@k0hT{c%xEAc`{cKteek1g1 z*e~FVP0ZZ-Zu_!Ip}~Rki937^BFHAdaOcApc!dczBJSmu$pw?w>E$M&nDJ zqX9+n{CL-_`p;YA(Lv7DgW|JeQn!>eg}KlRy1By`OeF1NF-)WAWva-4mc_6}?d8tCs5MI8-^alt!73HLK?=0H7k<%WDNeqP zxAFN~mcnE^!BlyS)Gny?MEf4HgQB-90Nv_miuKRzV{#2ET^>lnS*yL~k z4C+VvysViy*$(vHRA)MDzdz_%?@eNnW|$6zLaaJej|pUiw}2zI!8a+GNvP?uonU?Xs zJDKxpNaKxq{mAR5`j2_~<{gX+v;AQ4-C|9--Z-^s7p{k^oZ-+NbYz(0;n~AOb}~fy zAW#!JH?=+CNlgI|@bg0Do%_B8SZzUX&D&)5CgAGZxUQ)$8u)yj7h*A({h@3kSDzo{ z%xzV(d8=2$&c*H#&NQy_jdN_{UKXdlng>W(k4_0PKb;+(ZE$D`NK0iy*N~kO-2|th zCOj$wgV5dBLlx-mMO2^nBBR%bSDO^*)~TV6^~03CWG6Wy=uN7h#|B6LnFa=5;j`t+ z6L=~&KSi z!!2~}MsPsI1zse5*5?Z&b=zOHIf9e?zWhB& zn^3=x+f8;UkFi!avZ?i|B?XEyo^(3L@F8x3)PYXPgj7>XjM8WypGVz$X%YU^ihY^^ zjjb#j7`Z*L5GEBjFq%_1$ra_6Z;{WtlM=m%n4+=L2^UJkN>0u3U89FyDBt%<)WxQP z8bg$rz2x~jCP|@OBHNskoNzA!YfvJS_49S-w?U7PUwmp7V%*dnj+k>i>HD13aht{K z)vnMXp6R!*5YC(-4Y0LUuG#xFJm)rf3L(xB2B^uk5scJdW#P?JAgk$p6cJ`HiBjwZz2W8e! z4DD##!O%vG{i75-u54GCL=P+XkRz*sBs%<__=2e25yQF8YMd4=AyoEIf7+R;6AG1B z?erLn1t#^1#ejCHA6boi*g|?ww$AE@)8D3-`hSN|L*M6Jk(7QsymcZ8aJwNW+P>;QKH+$%M2qqf%q;jau8E^?3= zzS%=EU-t-cC1SamaaX*NJi6ie8l}LKqHdQ|a|Vc^jIG(aU#3Sc#%_nkvBV775h6lbfIuQw|BQq2g|Opobd3r7ur9mvRYxrJ8jfPub7W| zzHYRPv^E9!(>_(pSUS67UypAJlV6y@@Wn`lFcID|sj`QVhr=B-cqXN=BBY|YVhAXJ zdM&CZK=x)anB7FuK}_xSK{YPah;Pft7_C<+#+ZefQMBfX5bd-9Hc()dDC}6lX3+b; zeLwQ?13Bif8ewxBnqxJ(XN|f8JN$M>3Hg%frBu$K=N5WaOb4qUi#LlXZC<*3hDdv0 zG~EX4@ufbrt}$LI2hoC^HK0(Bq4qNkaIEgr>&djOPEjlN9y9ROJXWtm@LUdKiH`r^d-R^cQ&lBg>b z`xobj>o0C?uG=)H=Np5zDCv)$lf+pi^R~?Ij|nja=GS$w<8c)5+Vq3H_`gr&v6l*q{@!>9n9@o7d15ZDdRv%fv6h06cnuf~>*H7TdCo78mQ&1TCTl zRwp=#S&uUNeAVGI^m&221jV2qFzm@4_Qw$jF0(m!Miv{qFxt$v^2lpUZ*@#?+mG)6 z;4h=lP);Cq+%_9bO?;4Z)Y6r{t*fDA$-xqjb51Qb?>MFhV&fNZZV7Om3u=Dq;a@I)&>Hkm%LgTQq3ZuIOJS>A z+c=O0^GFMk=rh^j1x4>^GHRqL^T;H$Cl{4`msTNt-=km#5r)np#Zd-NEUuL+%nF0i z3gZHt$@JdS!_dT?zpr@aLsN*$UStsT70Z`kVDcgLLrRt|#rsy~)f0m7dp`c`PQ;Oo z|L^x4m61Pa)%?HTqckXWwx&2Y+n=QsavM&|*g7;w6LCBHAB2ZX*+MtjdD}q0j)^f#4O-9S2X^7GaCRv(jM?fX#uJT%T%_{wT@c ztL?kvsruvpyVsR##I;wr_8uuax~`FoP|_uIiOPtS(ID4ejTCkOxnYN(Oxx0Pr7 zfhod|w)vyO?|s6vqJI9q&tF`Dp1kq` ztCKM-Z6PeemKm^#mPACa;sr4#LR>c>&%ZGp24T;61+Av7RtBxAF*-})2H0_K15|Ow zSFH%r8qHm!A87OaTR`6D$P$@l-mYJx6og1}y7rLt^V;*&cRXQ)T3i$~(pM?uas1(1 z`T+HG!J@Ybzf(qb-GL1bz~g>u-zAO8_5^<4b+>ss^m)aJ>l!U(I*F-6w@Q#E3Ob;k zZm=nJMF@TRW*B%wpWvhw&PU3_aO^##eYd1T28Vtk`gd1=H#(pBeq+qp)^z)W}N4_jL$R}vy&cmElm`KRf(w`HX}H2>B~u^&z1 zgm<;TMquOD^Zo684PdfOgX#vcl|WN|dqcRY7C7Yj=V zuKw@^da2Z%_XA9;AG)%!I{0Z>?53j!%%1i^0`0HnQq@7fld%g;jLSs`hnBNVtoum= znyojSQg$CVvmCtRCX6q!F+C3P0D%+cIkI5{EZTz)(pLR~e!n_iNSm3t@#l;6(+D%A zLAGU=*FOWhGjYg4eI3iO`Spyd(^;YMP`!Yxl0s*tJb_dJ7OoS;Eubi3N)8we$Da z!zgfL-uhXj%TA?p{4X@3G{MO^cvKEck_<% zjt~R=r3Gt-e3#9+-qGF@_lLmEGy~Yd6+FR4Vu%lrSC69iv|7(tIxt%W|K0b@783o( z3!A;Jvd3cYEwJ`i;RSRUdq70#L6_FGM5N10JPKlu9AdWL1`)PNF?5dj;W(_78VdA) zmdAUD*L4FKQE)bogb+%9G&v2ZDO8=>&FqB=mcxzeK2UX9#@VHEDcDUi?_H>WS1he+ zW=c0_OO1OvSAD;5z~)rC%PwI=S$?e4DV$%~IqH+^-SoogSFHQv-az{z$is-$j}U2W z1yA&AV`yiRE^zj@;WdH3Y}=j~Q~USl+nD=fs`Ev_!gZh+j@f;F)a->>uD7r;g;d{) zEU}Nf^B)65Qw3v{dcBmoL5=xhZTVr4FMu;;?(leo{9nmeCij{h(!g3wBJl9a3(G%az->P^@iucFDU&7!fuuZY=FpW?aqi?Xa~UWl z#YHWOv`a}p<;R~ew@_*zZEwOl{OKYbnx_Vv09Q_P!Nj(}ancXc!db)*ciFi=7pp4C zg_+s@;esxAQ(zs$3tLAavv4Yscla2u3km0k{$iFBgG{PDPQkA@9rif(KEV>dtiG*G z#{2ZQAf1CK9%7v*+Dl8nrRQHzoqf+KmLd!l!AmXF@AcLBYqLZGx#$7irHWEkTx2+6`nD;$~Xu}WsN7C{YL(aAFDW8xP;5#TH-QRIuB2Ws3U z`0QtbG`vUNVNICL<8+G#d?Nj5N%O&e;bRtQW%R{&{tf z(R9sj!7M^7Yl)6!sMRiIIyi!vGxNE9rGS^qpk$kPAvN`w23HXMv8C z!7QTagDGU+QLE63{f@$18vs%ZghyJeu+tkhWx_wXd%}7gHrn&qlK~XcrIl( zvhKIW>MvHb9U*WAy$Z@-4g)R)8$2=_-4}b`f7#pUMJ4V&P4TO^MFMOm3I1q6bvus- ztU|XsT!=>LJ`q)-A7>TX8!-26`TN}$zh>_w|CS>)U6!+b)N1D;b}M!p<_FN2WmoyI zS8f*xz+1cj5k94!f0TijwI;$`aw;=gwDB!R&>^vbRAY~~DzHo~>9PE>z2#@$Go&Vk z?&gJpqQCb;JnUK)2mU!ZWL-Vi%GsVaRQ>`&+6SnF2m08TKT_^&KF8aJqm_}z1@*(e z(kj^%f!^8f?!w@U`8m+Ymr{iOEbfIM;Vh{Uy{N3J?7at*l^Ry=$oH^w5L;t%m2vp| zC_)8b#$|)Zs=qUK0NtU9VACNUZ*L z#%1rA(Ek6|n$LS%nNe4RR9oZelx?EDg{_8^RmNbQ+Ya&cjOa#bRh^(e{2DpZs(M+~ zl6a|`&h$iG(SoEaXTK1o$81+}3wB5j=~gB#rs`;$iJ+U#Q)wCF<%3USd;#$PgvA!y zIKQZWcB2WinNq#~B09u`!=|j{D$d97WUQ6DOi|!tE?h?F1*v{o?@kf2V3J-x2fZOT z>b)&D8op?cSb`O;#9$RCR2x^AmnAVV(S)v_1;898vlL%u42D6`)30pZ-sXJ+%^@^& zy2ufytk&z1q5AR#;qM_A8BlNEI!l6r+3O>3A%68ePRmsq`d0q5|4ju~01 z&rMvEG}>}dl$Dx%Fe~DS22%CN(y?m8#CY_rtAAft8TZBZt-}@MB-SUq^qO1N7mpyo z*4rd#@*JXb2-yFBZ^%xC9blFjrpOjnTQ|732^v?g{8Q%cj`!?VZNXC4;x$fPDd&s{ zgkiBQ+R$b9`dRu8xFp%1bWsY}HPOTjD-g6W=HZ%9`oiYy@_JUG+7>D5EclS6YwCAA zp$_0|-U}djuCP6c1Ow)U?Wg}#bFrNBxdFHjK}_R*%J-F2)orhA57f`RT3ayAj`7c4 zSo#U@V>LKDtDu6|H%lQ!LJo1*WvI{|yI$`1mX5zByltDnM13{s@JvA1K6gEDf`m?)Fm?RX;Kg1gK zO-#_=-~^%pxQ&2 z*;NloHk@Fp$OvgP;Qay~XE``{8;e!!11pD<8j6pKlM=`DJw7ut@h1J4v0e{pb8n^9 z!xy6C=TXosA!YUqBo_}XM(}%{MQoCsjmum}WM~!pLG=dvEmagBU(9Fd!yk}5lPz|m zL_$Atg!_O6VH)7>*4!IhJ`FiNjZv(kUss@ve*%17uk;hUlEr7rF`tg-Fmm-oK}Dd| z!)1^P;SOiG)aBSXT-U+8VF$^0c8CONauF2w&fzt8&C(tZgqB>X16ubCHp*)Wy58!{ z^`*Pnm@xlsGxRemmN~U`8m!cjgVA#en9V8@mUsd`+LALlV4pt+I^X%|z(S}mUsB8>IBt(lbEC=!o zZwgbYhDMMu3C#fTSf+#YSTfxw?nzG;%+}y7k`%#DS;##Y1Y)E3FyJ&b+O1${902b} zdj~5BpxQ}a;Dk(2Lx=o4jDS`E6M(Y7UBmU!e;OZ2c*=Ui3;&=5aIFv@wL|zJgw65Jkc~r%yfV5f%tyFoV*fm9rJwBTqN8kW<rMI8D?fPS}RPP^lXG{=5Vxj3*2HuO0FZH6Pa|_wOqkE_;K& zzcgA-X1(x6D5UqdqHCV({6pc2JzOdyH>;Qz1&ieMg2Y_#b7IJHW*{oIxOHf6{xp#f z8*G8ekx`l&GWi;5dPK2iAtW8MF~VIJRir*V&DI{mS))|S-1>7c$ji7Fy0ufiJKCad z$zj(ZB*GVSFTR$WTfuD)WbZWh*W}=EMvTGr7|e%<#|Q07AunDEq%)2QALmnWI49jJ z-_r)wd$?wzD#CX{Li9x8_^2UC4Jg!ERU;&1aSr%zH6l?tO#fSxAMovS2wPor%#l{{ zPHmkCUi>jm#ct~>9lwg4ivrsn*QgbA?naT!0*_@mk{_<-^C!U>*L|%Xz=1Is{!v|H zMT{&U-wXX3abUBbuRa<)2fE_TWdV|G17vGrZnRn(nrIR#5_ZlZF8U7ojCRZuvu*== zAs9_b_%8FV9ONyVYe{O?=Ccx%wcl42N8TY!zHL=K)pz&l@!BhQi)n1GvC%f_ODF)8 zy!-Dts{S^o1-`G%t7SFfPJ?Fo&KrVG7=FozxT_wrNnga!8@gr|?^{#A$nXLNZ=4aR z{S;ZU$QQ$~AD=D~w#peiMbd@ImMtGi1et)orC;LL3DrN}>;oc3T<1Ms?LRL^372T2 z+c<Qz8{Uf)HS!a+we-460y%DSdY+S2fHxBg5NDHFt)@Ho~=KP!w zy-Ko3K6f|WudL`yc*4H*Z#S-|iWc3`k4ByV$GM2E%6~6n!GU4p6jmm~V2TlGw!`1@ z3u%b*;aN2>lqA1hGJjJ1wHJ(7?FWB{y8(Jsq^Zhhx~t7C9-#jd){AC@0_pShC^{0u zB$!mX4=o&G4`U(?27JG!1+xJ9p$>IxmRZxH&?-rFv1mXd&1~jDjd6~juzOgq)xa+O z6r%QOrsr$+73D!}ibrQ8)@W}}lA-Xw-zWzP^$^SIaXrUC<#bf!+e^39bFDCAz*zc1 zCWK+Pc-L%JAeBP4%u@dZlF$KY!R*2$ult#>QctUo_r+Y|t1UTBIu85Rx(=Lo=OBZt zsPMu%_@F-x-eV~T0OtuJwgg}_sBA?bgR*eYqNXGE_=k*5k&P<1LD=$}d4~dC;a4BxmOzJ=G9~SeTCVSYDSN0bLm|z05+IIZ`%lX-IYS!&_F^amj#Tn! z`4w^UJ^&lW$olWMLDdke)(0~kdRO$vLO9Qu9d2bw+MD1%!3E*E z=1ecZ7WNck3A-kOe>Zj`#yQn}QWG1$GdElnj=+Hyod!k(&Y9ay0X34 z1f+N8HoLB>xeZk>qeA&vD?ea&_k$@Qd;A{)9{w>~9SI*@8Pv6$T|STA!RT zd+^_O&R__r`S8-GrT=#WFP$}w>o zAgGp!Whma$e+o1WJ#J_Q<7GJV1^V97d4i=>%`Z)@JF8$0*aiX2 z{mfvqE=(mlkdkyFL_gBaR6=!fUFE~G%l@m;>JmK6% zEs{d8F2P<9Cai+GpFtje;F*Fw>`RO;#yn*xHxm0!zS# zcREA7DYg~%)A3)S_i-u3B`^25b+wM@^29n3l#hL{swfpa8;%fj9tT-RydW%vC04o+ z21z$x7*eLaxUdUfReSNkDHMJF@D2zb{+?2$S|#L;i!+sr}wC#0$(2VuZh>`T1SWeR-edW$16gG+0?tn$I|ANJk_fX zI$kv53|j|}dWuPR7(Kh3Uue94f@UlFZf^>X3VnYs`du_VAxdj_5M=Fkm<_i#dvWj9 z5a8ZP7te;B^LDm;9!FX20G{mh_e3a;vEHtYsUgeTj!N~7mLv3h z!f=8;4$^Wrc%&l_=1CbnvBVF(CLqK+UT)%%DZFc86j5mHX zsNoV>a#kQlOiYX&P27R)z?>g>eNX%DFlJ{g#ksD!&0-mFMBaSNCDC^F9*pu0R*D&3 zy>uUl06tKc8Di-byrHnFTuZ-j)e2_C2Sl*q+duQk`v7)nC(o@wEK z$`34dlHV5d&i7S~h=u*FcbP!C-4_~>_Y}cjOmFF!F-X0 zl+;$G=^<>LeN7ltpxy~(RzAZ6q5zAsp`@o=Kl?3MN2IMDH=ANLY&v&8+UnZYJp&geg6G`RYY@;y#8DCB)Jng5@spN}uh6#ngQ2$) z!Re<)yF{`@o^p5W*#{^5ks!%DC9&<<64olk`HJ~t88Y3rrAkg7M}Uc^t>*-dB|$}B9bmoGBD%9 zYa~iv4uZzTla4NoW|1i97V|lZl6!2|iv3pe_ye1uj1tbo(axza?TC@X_dblYD@HPk z+ZQ<`ev9GzBYjN-SYj~nKec`)OO`QChb9*haqwgg6z%0sp%Ar%4*y4Pyf=lisYnK@ z{_}mlE70{mwmHT@{tB^2-OAD8sO-s?Q2!rMQGHqS{Eez-|7inzvIjI9b)1)U-y@?- z9=M4bSV5QXaztXL`~E2|j+kXJ_qRJrC|T|E!2ae}?dF9#t?C8bvRJ3kT3k!azfpH4m|8>n!bh5S`j z*I?e7^}te2tb(#YDB82joN%1*E(omO!%&vE1nmzRt_0z3*K0rp?V;JR)?J0x+YhNN z>~CcKvGiGhR@56tVVy&KU(4pY8VPpHYVS?JvM0-3gpCzaQ}L-q+#-d`a1MXP`-9@d z{C;T2zFVE&?1nWNxDEy1jv1YNf(&)Ib7ebM7{Uk36y0Ih+X?5bTX#3>+}mL7$Y1RO zVjV*Lmlui6=a_4etedCI&p6G~oI|cN3g@(n!wgyhJrNjx{_EPRB>PqR$>yRB{pfI7 zv73}3Ym9Ki{%c7)77z$*+hH@-Br(A%;1}`}B&*4JZY-#V=Gd1vtY};sQ#c%64)}eN zAAbgElpaly2R=iB>#;KD=v%2TmZvB*%u44vE8LIX_o0^!D_Q7}WZ+`vDwO1a7%f#c z{c+QFGK|H`yi+Ycs*4cx`R18(Q0yo8#2FAozVf;KyH)JRbimwL^g-%3r_&%EzYI`^ zJxl|7ePrjkdSMwcbX-UreJNpnn4Azl4PZqw`lX|oCeB!zt_=8*M{5U<)kvN7f=R`P zIj6GBTbW;qHbv<5!Oiibbc2Ri0^vOW>GzQy#S1jrkx8B9uEVpqBfIcZ+;n0r={KI} znbVm2SQm!x-aqAIVbloDMYBwOZvDGqVdv661WpO%)L+Wh9=rA2i#cDId#6v`LL=jk zRK7tn@#iyP@BiR)U_#6EG)+qLc;@t%42s40PF=O|ZH~Ki@pznA z5629XJDt8X&oOi_@zl>i%sjsd8vQCX_;t!|$*!3Cd#E19kAWQAMdZsy=pc(9&lW-s zTh{`C_38D21>08IL@o(RJe?xRgM`)Hr-8+WiCm$Jz#6TX>B(>HW1viuK{p0VXl~bJxckN^Nx{DT7>$dN?t8r?f~)=u zWMYYc!#Q>O1Hu=$UD$O$WGS{{UW!-0f@oMW1Zi^9vXliM+p#gX@?mT1?wd0XtTRCZDs0ShvR%vd|^li)zOdh&UYje`$ z^J@mLokb^bOvRz(5>Fdh()V9MQOvnK?j|5o?}xY?Qui8wn05eV$u)^sIvpU?&|>^$ z>YdB53A&D7To8j~I`hy72ROwmPMx4gU5L*g+zElQC*l*QZRJ#yG>T6VNf?slHi1|M z{upkF^v*DVAolp=^zMY2H)pswB(Bz$Cbpv4zRe!p{TECCQ!*lO3ARH#`aWEWJ7t_W z=`)zvTVshYqBSyjF6hdUO6ptDk@t7Th`>n8M|y4Gjqh_5&*1(wuSc*zzu%=^)1=H+ za|-6j%v#(MQ(0=OsPYr;Pm1^UkyHHh`1>2JSc<-#IVlA~B@9Z%9G@V5UHkNdbm1iz zlAa>n5HlKa+axEMTQoWml8^Q;rW`JkAgST+V(0!$mR=2YPdP8=?6810K8YHp@@prm z@N~Jb4^s+qDo2zPrKqfP(d#C1*hA;IZ~>D6OxR-Uy(JWE{*KC613_MyA?$vhN%5t%XnjJ>tq2aU(*>^z;a6u>1%2$pXs;@P7p z>y7^<;ndf7wJElBP_Ne zb#3I5mFVRpL${Uk)pX%F0!JjM|fM`P_n&4^KbLw%x+C3U*>TnkhK))ndvrijlSn#Vv5~ zna09c7k^UPkbh>V;nMbl4c--tjZ>;07VP)$AGY;jloPytVU!bs3XO%=?r>*+!R@9f z!wrUD!jftCD!wa!XLI@mO3oU`N}ZhH`Jgd@3ZXE^=)c!xr^mWu4q-2zf%x#{;S+>7 zRw@iM8jzE$TDlXIvzq;<{0+#5h3-R?)I*+L{ddPw&iCB@aMb27NvXtnXgx9#r(jd{ zhxG>9a-t;T3;97O_li~>D?9s=Qe!uZ>}ROoo(Pm!`o0gzA9k@SQ7a=kx`pMPuwNij zm_5yFd|+_JsMKuDX-p`4_uE*LdMZ&)S7#NDJ8 z#0##eSeb17!fyo=G|`RG>OHiz-=vnSnuksrFl~h_nfdItWxPVQ6Qt zs9FrGs8s6CFrr7z(0y|*IcCLgYm$tKH*|H)>Dh3_!n;{>=>o&oz3Ye%Sy9oo5z%kO zw>!g(t0UjErlYRokELg40+0F%{W2zJ)jW@g6L9jA?BH9 zjGUS>y34u^PLXMXiA)eMF?Qh#?(3Unk}j_3JkEY^FkS?wZ1liie3X)*g4Jd2+<#{} zhgKN>{;rMucc32+y#1oxowQXPog-x71}&m!tm!jt?(IC=vAc;Sc)BQzS-2%G_AEg(jElU6*fqMyE`e92im zX6q_7c{b!&KATw(@32Gf{l8~!0tb2} zBC*TIwPcsUrcj8jlfL~V2_9lvt?#w1E1oliE1t`+*FNTph)vi~_lkM%MXbW}bK{Be z+`>h(a&hm}9_8kngkJlX;U%j+kI|g!U3%cYwXG)XMy~k%>vV4+B9eqh;rrT1rC)M7 z_vmvAAI7ckO+1`Zn=P(l&*x!^K;+~41gM$5fIN@;FjmHk1n{FTaAFT?=svJ-0W+?_^&5EH5$@N~6s+sk`)xhrFoFaq#GOMD`~DB$W0TXOvO^oC(Qr5RUz7{= z_m5jKfSH@VJA@>CY~95s3UQ7IO|axIpVEJx(?74rp;Pd9bR4~{o9zF7J>ppf9v73T zz=i(z^ZezG*g@5JJ=XY>DlJuu)Q^&51)Mx114N= literal 38191 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 z (NRWtHMuz5nn_T=+Tzl%?#1x4Fl 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 +cron varchar(20) +UMLClass70515021090shares -- PK: a_id int(11) FK: email varchar(255) -symbol varchar(255) -UMLClass135150210130transaction +isin varchar(255) +comment varchar(255)UMLClass135150210130transaction -- PK: t_id int(11) FK: email varchar(255) -symbol varchar(255) +isin varchar(255) +comment varchar(255) time datetime count int(11) price float Relation335190110100lt=<<- -group=110;10;60;10;60;80;90;80Relation625190100100lt=<<-80;10;50;10;50;80;10;80Relation625260100160lt=<<-80;140;50;140;50;10;10;10UMLClass14041010030Database V0.1 \ No newline at end of file +group=110;10;60;10;60;80;90;80Relation625190100100lt=<<-80;10;50;10;50;80;10;80Relation625260100160lt=<<-80;140;50;140;50;10;10;10UMLClass14041010030Database V0.1UMLClass70515021090shares +-- +PK: a_id int(11) +FK: email varchar(255) +isin varchar(255) +comment varchar(255)UMLClass14030021090share_price +-- +PK: id int(11) +isin varchar(255) +price float +date datetime \ No newline at end of file From 0c9cc0b4ef3288183252a6dec52554e64891b11c Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:36:01 +0200 Subject: [PATCH 248/263] fixed /newtransaction --- telegram_bot/api_handling/api_handler.py | 1 + telegram_bot/bot.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index 63cfcdf..51e0ca1 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -288,6 +288,7 @@ class API_Handler: int: status code """ with r.Session() as s: + time = time[:-3] + "Z" # remove last character and add Z to make it a valid date for db headers = {'Authorization': 'Bearer ' + self.token + ":" + str(user_id)} transaction = {"comment": str(comment), "count": float(count), "isin": str(isin), "price": float(price), "time": str(time)} # set transaction as JSON with all the attributes needed according to Swagger docs req = s.post(self.db_adress + "/transaction", json=transaction, headers=headers) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 331b29d..90f1866 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -483,8 +483,8 @@ def set_new_transaction_step(message): amount = float(transaction_data[2]) price = float(transaction_data[3]) time = dt.datetime.now().isoformat() - #print("\n\n\n\n\n") - #print(f"{symbol},{amount},{price},{time}") + print("\n\n\n\n\n") + print(f"{isin},{amount},{price},{time}") status = api_handler.set_transaction(user_id, desc, isin, amount, price, time) if status == 200: From 3abf39a1a2ace69c44bce488af8262189ef5bfdb Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:41:00 +0200 Subject: [PATCH 249/263] deleted bot creds in main --- telegram_bot/api_handling/api_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index 51e0ca1..5d41e95 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -356,7 +356,7 @@ class API_Handler: if __name__ == "__main__": # editable, just for basic on the go testing of new functions print("This is a module for the telegram bot. It is not intended to be run directly.") - handler = API_Handler("https://gruppe1.testsites.info/api", "bot@example.com", "bot") + handler = API_Handler("https://gruppe1.testsites.info/api", "", "") # do not push with real credentials print(handler.token) keywords = handler.get_user_keywords(user_id = 1709356058) #user_id here is currently mine (Linus) print(keywords) From 6e3870fda779d296e4884c448461a0129706f223 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:41:14 +0200 Subject: [PATCH 250/263] deleted bot creds in main --- telegram_bot/api_handling/api_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index 5d41e95..bc44bb3 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -356,7 +356,7 @@ class API_Handler: if __name__ == "__main__": # editable, just for basic on the go testing of new functions print("This is a module for the telegram bot. It is not intended to be run directly.") - handler = API_Handler("https://gruppe1.testsites.info/api", "", "") # do not push with real credentials + handler = API_Handler("https://gruppe1.testsites.info/api", "", "") # do not push with real credentials!! print(handler.token) keywords = handler.get_user_keywords(user_id = 1709356058) #user_id here is currently mine (Linus) print(keywords) From 4255c82667a79585a2dc8c5ba8007d9032d58f73 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:42:20 +0200 Subject: [PATCH 251/263] updated date and version --- telegram_bot/api_handling/api_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index bc44bb3..50375a1 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -2,8 +2,8 @@ script for communicating with webservice to get data from database """ __author__ = "Florian Kellermann, Linus Eickhoff" -__date__ = "16.03.2022" -__version__ = "0.0.1" +__date__ = "26.04.2022" +__version__ = "1.0.1" __license__ = "None" #side-dependencies: none From 4ec81253939b6bfc5e2247665391ec3ed1217bf8 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:44:52 +0200 Subject: [PATCH 252/263] creds now are pulled from env --- telegram_bot/api_handling/api_handler.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index 50375a1..5975592 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -14,6 +14,9 @@ import sys import os import requests as r from croniter import croniter # used for checking cron formatting +from dotenv import load_dotenv + +load_dotenv() # loads environment vars # note: for more information about the api visit swagger documentation on https://gruppe1.testsites.info/api/docs#/ @@ -356,7 +359,7 @@ class API_Handler: if __name__ == "__main__": # editable, just for basic on the go testing of new functions print("This is a module for the telegram bot. It is not intended to be run directly.") - handler = API_Handler("https://gruppe1.testsites.info/api", "", "") # do not push with real credentials!! + handler = API_Handler("https://gruppe1.testsites.info/api", str(os.getenv("BOT_EMAIL")), str(os.getenv("BOT_PASSWORD"))) # get creds from env print(handler.token) keywords = handler.get_user_keywords(user_id = 1709356058) #user_id here is currently mine (Linus) print(keywords) From dbc93cc31a7c0e3d5a4b49c9341b25040d4cfde3 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:46:20 +0200 Subject: [PATCH 253/263] updated version and date --- telegram_bot/bot.py | 4 ++-- telegram_bot/bot_updates.py | 4 ++-- telegram_bot/news/news_fetcher.py | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 90f1866..c17734f 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -3,8 +3,8 @@ script for telegram bot and its functions """ __author__ = "Florian Kellermann, Linus Eickhoff" -__date__ = "11.03.2022" -__version__ = "0.0.4" +__date__ = "26.04.2022" +__version__ = "1.2.2" __license__ = "None" # side-dependencies: none diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 4853df0..ff76299 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -2,8 +2,8 @@ script for regularly sending updates on shares and news based on user interval """ __author__ = "Florian Kellermann, Linus Eickhoff" -__date__ = "05.04.2022" -__version__ = "1.0.1" +__date__ = "26.04.2022" +__version__ = "1.0.2" __license__ = "None" from asyncio.windows_events import NULL diff --git a/telegram_bot/news/news_fetcher.py b/telegram_bot/news/news_fetcher.py index a6dad7e..bd083b5 100644 --- a/telegram_bot/news/news_fetcher.py +++ b/telegram_bot/news/news_fetcher.py @@ -2,15 +2,13 @@ script for news fetching (by keywords) """ __author__ = "Florian Kellermann, Linus Eickhoff" -__date__ = "15.03.2022" -__version__ = "0.0.1" +__date__ = "26.04.2022" +__version__ = "1.0.0" __license__ = "None" import sys import os -import json import requests -import datetime as dt from newsapi import NewsApiClient from dotenv import load_dotenv From fa569ff5b2c17fde1284a361bad7d7e511802fc7 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:47:44 +0200 Subject: [PATCH 254/263] removed unused imports --- telegram_bot/api_handling/api_handler.py | 1 - telegram_bot/bot.py | 2 -- telegram_bot/bot_updates.py | 6 ------ 3 files changed, 9 deletions(-) diff --git a/telegram_bot/api_handling/api_handler.py b/telegram_bot/api_handling/api_handler.py index 5975592..35aef19 100644 --- a/telegram_bot/api_handling/api_handler.py +++ b/telegram_bot/api_handling/api_handler.py @@ -9,7 +9,6 @@ __license__ = "None" #side-dependencies: none #Work in Progress -from email import header import sys import os import requests as r diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index c17734f..1c5e92e 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -14,13 +14,11 @@ __license__ = "None" # API Documentation https://core.telegram.org/bots/api # Code examples https://github.com/eternnoir/pyTelegramBotAPI#getting-started -import email import os import telebot import sys import logging -import json import re import news.news_fetcher as news diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index ff76299..22ba398 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -6,18 +6,12 @@ __date__ = "26.04.2022" __version__ = "1.0.2" __license__ = "None" -from asyncio.windows_events import NULL -from calendar import month # unused, remove? -from symtable import Symbol # unused, remove? from dotenv import load_dotenv -from shares.share_fetcher import get_share_price # unused, remove? import news.news_fetcher as news_fetcher import time -import datetime # unused, remove? import os from bot import bot import sys -from multiprocessing import Process # unused, remove? from apscheduler.schedulers.background import BackgroundScheduler from api_handling.api_handler import API_Handler From e96bac3865d75bc6fb9a37a14943a5e8c548024c Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:49:19 +0200 Subject: [PATCH 255/263] removed unused imports --- telegram_bot/shares/share_fetcher.py | 1 - 1 file changed, 1 deletion(-) diff --git a/telegram_bot/shares/share_fetcher.py b/telegram_bot/shares/share_fetcher.py index 1aa3376..c7ec63f 100644 --- a/telegram_bot/shares/share_fetcher.py +++ b/telegram_bot/shares/share_fetcher.py @@ -7,7 +7,6 @@ __version__ = "0.0.2" __license__ = "None" import yfinance -import json def get_share_price(str_symbol): From fffc6a966b877a02db922d6252880750bf3255a1 Mon Sep 17 00:00:00 2001 From: Linus E <75929322+Rripped@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:54:22 +0200 Subject: [PATCH 256/263] small comment --- telegram_bot/bot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 1c5e92e..346a610 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -280,7 +280,7 @@ def send_share_update(message): def send_share_price(message): str_share_price = share_fetcher.get_share_price(str(message.text)) - bot.reply_to(message, str_share_price) + bot.reply_to(message, str_share_price) # add dollar symbol etc. @bot.message_handler(commands=['allnews', 'Allnews']) # /allnews -> get all news From 050a1326e9058fd46bdcf9a99924c4fbc33cdbe1 Mon Sep 17 00:00:00 2001 From: Florian Kellermann Date: Tue, 26 Apr 2022 16:32:45 +0200 Subject: [PATCH 257/263] Link mistake fixed --- telegram_bot/bot_updates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram_bot/bot_updates.py b/telegram_bot/bot_updates.py index 46df917..3bca738 100644 --- a/telegram_bot/bot_updates.py +++ b/telegram_bot/bot_updates.py @@ -146,7 +146,7 @@ def update_for_user(p_user_id, p_my_handler): my_update_message = f'Symbol: {share_symbols[i]}\nCurrent Price per Share: {share_courses[i]}\nAmount owned: {share_amounts[i]}\nTotal Investment: {float(share_courses[i]) * float(share_amounts[i])}' send_to_user(my_update_message, pUser_id=p_user_id) else: - send_to_user("No shares found for your account. Check https://gruppe1.testsites.info/api to change your settings and add shares.", pUser_id=p_user_id) + send_to_user("No shares found for your account. Check https://gruppe1.testsites.info to change your settings and add shares.", pUser_id=p_user_id) keywords = p_my_handler.get_user_keywords(p_user_id) # get keywords as array From 3404c1a18673bd8302d7724928bbb7b125e0f45e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Apr 2022 20:06:46 +0000 Subject: [PATCH 258/263] Bump karma from 6.3.18 to 6.3.19 in /frontend Bumps [karma](https://github.com/karma-runner/karma) from 6.3.18 to 6.3.19. - [Release notes](https://github.com/karma-runner/karma/releases) - [Changelog](https://github.com/karma-runner/karma/blob/master/CHANGELOG.md) - [Commits](https://github.com/karma-runner/karma/compare/v6.3.18...v6.3.19) --- updated-dependencies: - dependency-name: karma dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 14 +++++++------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7c9bec1..993b3a2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -31,7 +31,7 @@ "@types/jasmine": "~4.0.3", "@types/node": "^17.0.27", "jasmine-core": "~4.1.0", - "karma": "~6.3.18", + "karma": "~6.3.19", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.0.0", @@ -6962,9 +6962,9 @@ ] }, "node_modules/karma": { - "version": "6.3.18", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.18.tgz", - "integrity": "sha512-YEwXVHRILKWKN7uEW9IkgTPjnYGb3YA3MDvlp04xpSRAyrNPoRmsBayLDgHykKAwBm6/mAOckj4xi/1JdQfhzQ==", + "version": "6.3.19", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.19.tgz", + "integrity": "sha512-NDhWckzES/Y9xMiddyU1RzaKL76/scCsu8Mp0vR0Z3lQRvC3p72+Ab4ppoxs36S9tyPNX5V48yvaV++RNEBPZw==", "dev": true, "dependencies": { "@colors/colors": "1.5.0", @@ -16534,9 +16534,9 @@ "dev": true }, "karma": { - "version": "6.3.18", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.18.tgz", - "integrity": "sha512-YEwXVHRILKWKN7uEW9IkgTPjnYGb3YA3MDvlp04xpSRAyrNPoRmsBayLDgHykKAwBm6/mAOckj4xi/1JdQfhzQ==", + "version": "6.3.19", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.19.tgz", + "integrity": "sha512-NDhWckzES/Y9xMiddyU1RzaKL76/scCsu8Mp0vR0Z3lQRvC3p72+Ab4ppoxs36S9tyPNX5V48yvaV++RNEBPZw==", "dev": true, "requires": { "@colors/colors": "1.5.0", diff --git a/frontend/package.json b/frontend/package.json index a438817..c8f0e94 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -32,7 +32,7 @@ "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.3", "@types/node": "^17.0.27", - "karma": "~6.3.18", + "karma": "~6.3.19", "jasmine-core": "~4.1.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.2.0", From 8fcdd56c86cc4e61c8ae226149981181b3ec051a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Apr 2022 09:38:11 +0000 Subject: [PATCH 259/263] Bump @types/node from 17.0.27 to 17.0.29 in /frontend Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 17.0.27 to 17.0.29. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 14 +++++++------- frontend/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 993b3a2..dfd50d2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -29,7 +29,7 @@ "@angular/cli": "~13.3.3", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.3", - "@types/node": "^17.0.27", + "@types/node": "^17.0.29", "jasmine-core": "~4.1.0", "karma": "~6.3.19", "karma-chrome-launcher": "~3.1.0", @@ -2737,9 +2737,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "17.0.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.27.tgz", - "integrity": "sha512-4/Ke7bbWOasuT3kceBZFGakP1dYN2XFd8v2l9bqF2LNWrmeU07JLpp56aEeG6+Q3olqO5TvXpW0yaiYnZJ5CXg==", + "version": "17.0.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.29.tgz", + "integrity": "sha512-tx5jMmMFwx7wBwq/V7OohKDVb/JwJU5qCVkeLMh1//xycAJ/ESuw9aJ9SEtlCZDYi2pBfe4JkisSoAtbOsBNAA==", "dev": true }, "node_modules/@types/parse-json": { @@ -13392,9 +13392,9 @@ "dev": true }, "@types/node": { - "version": "17.0.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.27.tgz", - "integrity": "sha512-4/Ke7bbWOasuT3kceBZFGakP1dYN2XFd8v2l9bqF2LNWrmeU07JLpp56aEeG6+Q3olqO5TvXpW0yaiYnZJ5CXg==", + "version": "17.0.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.29.tgz", + "integrity": "sha512-tx5jMmMFwx7wBwq/V7OohKDVb/JwJU5qCVkeLMh1//xycAJ/ESuw9aJ9SEtlCZDYi2pBfe4JkisSoAtbOsBNAA==", "dev": true }, "@types/parse-json": { diff --git a/frontend/package.json b/frontend/package.json index c8f0e94..b1035b3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,7 +31,7 @@ "@angular/cli": "~13.3.3", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.3", - "@types/node": "^17.0.27", + "@types/node": "^17.0.29", "karma": "~6.3.19", "jasmine-core": "~4.1.0", "karma-chrome-launcher": "~3.1.0", From 009164f572b2f82b7fbcbb949f9e8ad04be8ff6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 09:30:23 +0000 Subject: [PATCH 260/263] Update faker requirement from ~=13.4.0 to ~=13.6.0 in /api Updates the requirements on [faker](https://github.com/joke2k/faker) to permit the latest version. - [Release notes](https://github.com/joke2k/faker/releases) - [Changelog](https://github.com/joke2k/faker/blob/master/CHANGELOG.md) - [Commits](https://github.com/joke2k/faker/compare/v13.4.0...v13.6.0) --- updated-dependencies: - dependency-name: faker dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- api/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/requirements.txt b/api/requirements.txt index dcc83f1..90bed9a 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -11,6 +11,6 @@ bcrypt==3.2.0 pytest~=7.1.2 pytest-cov marshmallow~=3.15.0 -faker~=13.4.0 +faker~=13.6.0 yfinance~=0.1.70 requests~=2.27.1 \ No newline at end of file From f1aee6b384b3f624888424ae470c9601d40291f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 09:33:02 +0000 Subject: [PATCH 261/263] Bump @angular/material from 13.3.4 to 13.3.5 in /frontend Bumps [@angular/material](https://github.com/angular/components) from 13.3.4 to 13.3.5. - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/13.3.4...13.3.5) --- updated-dependencies: - dependency-name: "@angular/material" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 28 ++++++++++++++-------------- frontend/package.json | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 993b3a2..1367dc1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,7 +14,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.3.4", + "@angular/material": "^13.3.5", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", @@ -352,9 +352,9 @@ } }, "node_modules/@angular/cdk": { - "version": "13.3.4", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.4.tgz", - "integrity": "sha512-im4LKxJaIuqFVzmtf650PoiYsn/SZlvBV2zEgzusK8HwQ24C1Lya7NQSApwl8k43h4eKO1OvUKBjqL5uDgEQag==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.5.tgz", + "integrity": "sha512-fA99fGgybup9ezyB/IzOa9Mk8g8LjejkqikLnC3mAeQ0lROOO7Vf9Rp1v7/ahe2lALTUbA1bzJeXzQYaffkIiA==", "dependencies": { "tslib": "^2.3.0" }, @@ -584,15 +584,15 @@ } }, "node_modules/@angular/material": { - "version": "13.3.4", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.4.tgz", - "integrity": "sha512-jK9rWmBaPrE+3re6uIdyvG5DCzc47VUvnP1DqblNnpaa8GCKllb1cFRGqa5GPYB1/96d3wO+RPwzhC06qqV+yw==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.5.tgz", + "integrity": "sha512-4+FCb6Tbre5SwhZRKfnuh8K+/o+DuCGisJOuk7lxdFKDGDPjsPDWYVrBDal1N70mO09z/ApwNjpsIjuRv79wpg==", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { "@angular/animations": "^13.0.0 || ^14.0.0-0", - "@angular/cdk": "13.3.4", + "@angular/cdk": "13.3.5", "@angular/common": "^13.0.0 || ^14.0.0-0", "@angular/core": "^13.0.0 || ^14.0.0-0", "@angular/forms": "^13.0.0 || ^14.0.0-0", @@ -11678,9 +11678,9 @@ } }, "@angular/cdk": { - "version": "13.3.4", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.4.tgz", - "integrity": "sha512-im4LKxJaIuqFVzmtf650PoiYsn/SZlvBV2zEgzusK8HwQ24C1Lya7NQSApwl8k43h4eKO1OvUKBjqL5uDgEQag==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.5.tgz", + "integrity": "sha512-fA99fGgybup9ezyB/IzOa9Mk8g8LjejkqikLnC3mAeQ0lROOO7Vf9Rp1v7/ahe2lALTUbA1bzJeXzQYaffkIiA==", "requires": { "parse5": "^5.0.0", "tslib": "^2.3.0" @@ -11840,9 +11840,9 @@ } }, "@angular/material": { - "version": "13.3.4", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.4.tgz", - "integrity": "sha512-jK9rWmBaPrE+3re6uIdyvG5DCzc47VUvnP1DqblNnpaa8GCKllb1cFRGqa5GPYB1/96d3wO+RPwzhC06qqV+yw==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.5.tgz", + "integrity": "sha512-4+FCb6Tbre5SwhZRKfnuh8K+/o+DuCGisJOuk7lxdFKDGDPjsPDWYVrBDal1N70mO09z/ApwNjpsIjuRv79wpg==", "requires": { "tslib": "^2.3.0" } diff --git a/frontend/package.json b/frontend/package.json index c8f0e94..36fc979 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,7 @@ "@angular/compiler": "~13.2.0", "@angular/core": "~13.2.0", "@angular/forms": "~13.2.0", - "@angular/material": "^13.3.4", + "@angular/material": "^13.3.5", "@angular/platform-browser": "~13.2.0", "@angular/platform-browser-dynamic": "~13.2.0", "@angular/router": "~13.2.0", From 1806634b340506c3cfdcf25aff6159a1467e1845 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 09:35:04 +0000 Subject: [PATCH 262/263] Bump @angular-devkit/build-angular from 13.3.3 to 13.3.4 in /frontend Bumps [@angular-devkit/build-angular](https://github.com/angular/angular-cli) from 13.3.3 to 13.3.4. - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/13.3.3...13.3.4) --- updated-dependencies: - dependency-name: "@angular-devkit/build-angular" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 738 +++++++++++++++++++++++++++++-------- frontend/package.json | 2 +- 2 files changed, 582 insertions(+), 158 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 993b3a2..5d03484 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,7 +25,7 @@ "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.3.3", + "@angular-devkit/build-angular": "~13.3.4", "@angular/cli": "~13.3.3", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.3", @@ -86,15 +86,15 @@ "dev": true }, "node_modules/@angular-devkit/build-angular": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.3.tgz", - "integrity": "sha512-iEpNF3tF+9Gw+qQKL63fPFHIvWokJdrgVU4GzENQ5QeL8zk8iYTEbH3jWogq5tWy5+VmNP/mKkasq9i78lRiYw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.4.tgz", + "integrity": "sha512-z74cmDi2V+5XpvyZKFlUXxvQ446shxyZk5aGdToG6n+0/IJWkDXSiryQkCo8nblGMze7HKf75i3DsGWYQZLDnQ==", "dev": true, "dependencies": { "@ampproject/remapping": "1.1.1", - "@angular-devkit/architect": "0.1303.3", - "@angular-devkit/build-webpack": "0.1303.3", - "@angular-devkit/core": "13.3.3", + "@angular-devkit/architect": "0.1303.4", + "@angular-devkit/build-webpack": "0.1303.4", + "@angular-devkit/core": "13.3.4", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -105,7 +105,7 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.3.3", + "@ngtools/webpack": "13.3.4", "ansi-colors": "4.1.1", "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", @@ -195,6 +195,48 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { + "version": "0.1303.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.4.tgz", + "integrity": "sha512-d6YmIWdYvwk6WaknHRcJgiXeJvX9K5i8uPMAaL2P2/LU8n3moIQ59C7SP0uULcHuuiREEmFWOyyrWnGxZCI9bg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.4", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.4.tgz", + "integrity": "sha512-gj6i8ksPaT2bvYwI7wKJxLX53pHfTmZc1RaNbAGfZB1/zFNnb3MPj8utTcJSk4qMsGXuDDhiB7hpTKBw8ROaGA==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -220,12 +262,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1303.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.3.tgz", - "integrity": "sha512-v/z/YgwrAzYn1LfN9OHNxqcThyyg4LLx28hmHzDs5gyDShAK189y34EoT9uQ+lCyQrPVhP7UKACCxCdSwOEJiA==", + "version": "0.1303.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.4.tgz", + "integrity": "sha512-3F10P9XshRXkI/PEmJUcgP4yK4sobaoInQfifzPNOemrS5nXs8y3uEiQuxzyswYx/dymZLV+19sV/eh1WfXnBA==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1303.3", + "@angular-devkit/architect": "0.1303.4", "rxjs": "6.6.7" }, "engines": { @@ -238,6 +280,48 @@ "webpack-dev-server": "^4.0.0" } }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { + "version": "0.1303.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.4.tgz", + "integrity": "sha512-d6YmIWdYvwk6WaknHRcJgiXeJvX9K5i8uPMAaL2P2/LU8n3moIQ59C7SP0uULcHuuiREEmFWOyyrWnGxZCI9bg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.4", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.4.tgz", + "integrity": "sha512-gj6i8ksPaT2bvYwI7wKJxLX53pHfTmZc1RaNbAGfZB1/zFNnb3MPj8utTcJSk4qMsGXuDDhiB7hpTKBw8ROaGA==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -2413,9 +2497,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.3.tgz", - "integrity": "sha512-O6EzafKfFuvI3Ju941u7ANs0mT7YDdChbVRhVECCPWOTm3Klr73js3bnCDzaJlxZNjzlG/KeUu5ghrhbMrHjSw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.4.tgz", + "integrity": "sha512-dNDNeAOwtpX5A7TTEsgDbkg4jTmAJHD96qLqcpJqfBg8nZ4mqn6E0HinX9HZKaCST1/75T6GsFo1Muc4MsHYgA==", "dev": true, "engines": { "node": "^12.20.0 || ^14.15.0 || >=16.10.0", @@ -2761,9 +2845,9 @@ "dev": true }, "node_modules/@types/retry": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", - "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, "node_modules/@types/serve-index": { @@ -3477,24 +3561,27 @@ } }, "node_modules/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "dev": true, "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/body-parser/node_modules/debug": { @@ -3506,12 +3593,33 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/bonjour": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", @@ -4658,10 +4766,14 @@ } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/detect-node": { "version": "2.1.0", @@ -5436,38 +5548,39 @@ } }, "node_modules/express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.0.tgz", + "integrity": "sha512-EJEXxiTQJS3lIPrU1AE2vRuT7X7E+0KBbpm5GSoK524yl0K8X+er8zS2P14E64eqsVNoWbMCT7MpmQ+ErAhgRg==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.2", + "body-parser": "1.20.0", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.2", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.9.7", + "qs": "6.10.3", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "0.18.0", + "serve-static": "1.15.0", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -5482,6 +5595,15 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5491,12 +5613,51 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/express/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -5517,6 +5678,15 @@ } ] }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -5791,6 +5961,15 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gauge": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.3.tgz", @@ -6086,19 +6265,37 @@ "dev": true }, "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, "dependencies": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" } }, "node_modules/http-parser-js": { @@ -6136,9 +6333,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz", - "integrity": "sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", "dev": true, "dependencies": { "@types/http-proxy": "^1.17.8", @@ -8232,6 +8429,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-is": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", @@ -8488,12 +8694,12 @@ } }, "node_modules/p-retry": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", - "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "dev": true, "dependencies": { - "@types/retry": "^0.12.0", + "@types/retry": "0.12.0", "retry": "^0.13.1" }, "engines": { @@ -9389,10 +9595,13 @@ } }, "node_modules/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" }, @@ -9439,13 +9648,13 @@ } }, "node_modules/raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dev": true, "dependencies": { "bytes": "3.1.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -9547,13 +9756,14 @@ "dev": true }, "node_modules/regexp.prototype.flags": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.2.tgz", - "integrity": "sha512-Ynz8fTQW5/1elh+jWU2EDDzeoNbD0OQ0R+D1VJU5ATOkUaro4A9YEkdN2ODQl/8UQFPPpZNw91fOcLFamM7Pww==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" }, "engines": { "node": ">= 0.4" @@ -9940,24 +10150,24 @@ } }, "node_modules/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dev": true, "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" @@ -9978,6 +10188,15 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/send/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -9996,6 +10215,27 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/serialize-javascript": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", @@ -10066,15 +10306,15 @@ "dev": true }, "node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dev": true, "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" }, "engines": { "node": ">= 0.8.0" @@ -10125,6 +10365,20 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -11484,15 +11738,15 @@ } }, "@angular-devkit/build-angular": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.3.tgz", - "integrity": "sha512-iEpNF3tF+9Gw+qQKL63fPFHIvWokJdrgVU4GzENQ5QeL8zk8iYTEbH3jWogq5tWy5+VmNP/mKkasq9i78lRiYw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.4.tgz", + "integrity": "sha512-z74cmDi2V+5XpvyZKFlUXxvQ446shxyZk5aGdToG6n+0/IJWkDXSiryQkCo8nblGMze7HKf75i3DsGWYQZLDnQ==", "dev": true, "requires": { "@ampproject/remapping": "1.1.1", - "@angular-devkit/architect": "0.1303.3", - "@angular-devkit/build-webpack": "0.1303.3", - "@angular-devkit/core": "13.3.3", + "@angular-devkit/architect": "0.1303.4", + "@angular-devkit/build-webpack": "0.1303.4", + "@angular-devkit/core": "13.3.4", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -11503,7 +11757,7 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.3.3", + "@ngtools/webpack": "13.3.4", "ansi-colors": "4.1.1", "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", @@ -11556,6 +11810,30 @@ "webpack-subresource-integrity": "5.1.0" }, "dependencies": { + "@angular-devkit/architect": { + "version": "0.1303.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.4.tgz", + "integrity": "sha512-d6YmIWdYvwk6WaknHRcJgiXeJvX9K5i8uPMAaL2P2/LU8n3moIQ59C7SP0uULcHuuiREEmFWOyyrWnGxZCI9bg==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.4", + "rxjs": "6.6.7" + } + }, + "@angular-devkit/core": { + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.4.tgz", + "integrity": "sha512-gj6i8ksPaT2bvYwI7wKJxLX53pHfTmZc1RaNbAGfZB1/zFNnb3MPj8utTcJSk4qMsGXuDDhiB7hpTKBw8ROaGA==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -11582,15 +11860,39 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.1303.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.3.tgz", - "integrity": "sha512-v/z/YgwrAzYn1LfN9OHNxqcThyyg4LLx28hmHzDs5gyDShAK189y34EoT9uQ+lCyQrPVhP7UKACCxCdSwOEJiA==", + "version": "0.1303.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.4.tgz", + "integrity": "sha512-3F10P9XshRXkI/PEmJUcgP4yK4sobaoInQfifzPNOemrS5nXs8y3uEiQuxzyswYx/dymZLV+19sV/eh1WfXnBA==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1303.3", + "@angular-devkit/architect": "0.1303.4", "rxjs": "6.6.7" }, "dependencies": { + "@angular-devkit/architect": { + "version": "0.1303.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.4.tgz", + "integrity": "sha512-d6YmIWdYvwk6WaknHRcJgiXeJvX9K5i8uPMAaL2P2/LU8n3moIQ59C7SP0uULcHuuiREEmFWOyyrWnGxZCI9bg==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.4", + "rxjs": "6.6.7" + } + }, + "@angular-devkit/core": { + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.4.tgz", + "integrity": "sha512-gj6i8ksPaT2bvYwI7wKJxLX53pHfTmZc1RaNbAGfZB1/zFNnb3MPj8utTcJSk4qMsGXuDDhiB7hpTKBw8ROaGA==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -13114,9 +13416,9 @@ } }, "@ngtools/webpack": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.3.tgz", - "integrity": "sha512-O6EzafKfFuvI3Ju941u7ANs0mT7YDdChbVRhVECCPWOTm3Klr73js3bnCDzaJlxZNjzlG/KeUu5ghrhbMrHjSw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.4.tgz", + "integrity": "sha512-dNDNeAOwtpX5A7TTEsgDbkg4jTmAJHD96qLqcpJqfBg8nZ4mqn6E0HinX9HZKaCST1/75T6GsFo1Muc4MsHYgA==", "dev": true, "requires": {} }, @@ -13416,9 +13718,9 @@ "dev": true }, "@types/retry": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", - "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, "@types/serve-index": { @@ -13994,21 +14296,23 @@ } }, "body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "dev": true, "requires": { "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "dependencies": { "debug": { @@ -14020,11 +14324,26 @@ "ms": "2.0.0" } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } } } }, @@ -14878,9 +15197,9 @@ "dev": true }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true }, "detect-node": { @@ -15388,38 +15707,39 @@ } }, "express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.0.tgz", + "integrity": "sha512-EJEXxiTQJS3lIPrU1AE2vRuT7X7E+0KBbpm5GSoK524yl0K8X+er8zS2P14E64eqsVNoWbMCT7MpmQ+ErAhgRg==", "dev": true, "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.2", + "body-parser": "1.20.0", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.2", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.9.7", + "qs": "6.10.3", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "0.18.0", + "serve-static": "1.15.0", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -15431,6 +15751,12 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -15440,17 +15766,53 @@ "ms": "2.0.0" } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true } } }, @@ -15660,6 +16022,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, "gauge": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.3.tgz", @@ -15897,16 +16265,30 @@ "dev": true }, "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, "requires": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } } }, "http-parser-js": { @@ -15938,9 +16320,9 @@ } }, "http-proxy-middleware": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz", - "integrity": "sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", "dev": true, "requires": { "@types/http-proxy": "^1.17.8", @@ -17495,6 +17877,12 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true + }, "object-is": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", @@ -17675,12 +18063,12 @@ } }, "p-retry": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.1.tgz", - "integrity": "sha512-e2xXGNhZOZ0lfgR9kL34iGlU8N/KO0xZnQxVEwdeOvpqNDQfdnxIYizvWtK8RglUa3bGqI8g0R/BdfzLMxRkiA==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "dev": true, "requires": { - "@types/retry": "^0.12.0", + "@types/retry": "0.12.0", "retry": "^0.13.1" }, "dependencies": { @@ -18314,10 +18702,13 @@ "dev": true }, "qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", - "dev": true + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } }, "queue-microtask": { "version": "1.2.3", @@ -18341,13 +18732,13 @@ "dev": true }, "raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dev": true, "requires": { "bytes": "3.1.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -18434,13 +18825,14 @@ "dev": true }, "regexp.prototype.flags": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.2.tgz", - "integrity": "sha512-Ynz8fTQW5/1elh+jWU2EDDzeoNbD0OQ0R+D1VJU5ATOkUaro4A9YEkdN2ODQl/8UQFPPpZNw91fOcLFamM7Pww==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" } }, "regexpu-core": { @@ -18709,24 +19101,24 @@ } }, "send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dev": true, "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "debug": { @@ -18746,6 +19138,12 @@ } } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -18757,6 +19155,21 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true } } }, @@ -18826,15 +19239,15 @@ } }, "serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" } }, "set-blocking": { @@ -18873,6 +19286,17 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", diff --git a/frontend/package.json b/frontend/package.json index c8f0e94..5a55e8d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,7 +27,7 @@ "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.3.3", + "@angular-devkit/build-angular": "~13.3.4", "@angular/cli": "~13.3.3", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.3", From 3ba332c2c283312c8679af290582c19a84997625 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 17:09:11 +0000 Subject: [PATCH 263/263] Bump @angular/cli from 13.3.3 to 13.3.4 in /frontend Bumps [@angular/cli](https://github.com/angular/angular-cli) from 13.3.3 to 13.3.4. - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/master/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/13.3.3...13.3.4) --- updated-dependencies: - dependency-name: "@angular/cli" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 226 ++++++++----------------------------- frontend/package.json | 2 +- 2 files changed, 48 insertions(+), 180 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4bbdbba..7bc2131 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -26,7 +26,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "~13.3.4", - "@angular/cli": "~13.3.3", + "@angular/cli": "~13.3.4", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.3", "@types/node": "^17.0.29", @@ -53,12 +53,12 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1303.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.3.tgz", - "integrity": "sha512-WRVVBCzLlMqRZVhZXGASHzNJK/OCAvl/DTGhlLuJDIjF7lVGnXHjtwNM8ilYZq949OnC3fly5Z61TfhbN/OHCg==", + "version": "0.1303.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.4.tgz", + "integrity": "sha512-d6YmIWdYvwk6WaknHRcJgiXeJvX9K5i8uPMAaL2P2/LU8n3moIQ59C7SP0uULcHuuiREEmFWOyyrWnGxZCI9bg==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.3.3", + "@angular-devkit/core": "13.3.4", "rxjs": "6.6.7" }, "engines": { @@ -195,48 +195,6 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { - "version": "0.1303.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.4.tgz", - "integrity": "sha512-d6YmIWdYvwk6WaknHRcJgiXeJvX9K5i8uPMAaL2P2/LU8n3moIQ59C7SP0uULcHuuiREEmFWOyyrWnGxZCI9bg==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "13.3.4", - "rxjs": "6.6.7" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "13.3.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.4.tgz", - "integrity": "sha512-gj6i8ksPaT2bvYwI7wKJxLX53pHfTmZc1RaNbAGfZB1/zFNnb3MPj8utTcJSk4qMsGXuDDhiB7hpTKBw8ROaGA==", - "dev": true, - "dependencies": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -280,48 +238,6 @@ "webpack-dev-server": "^4.0.0" } }, - "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { - "version": "0.1303.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.4.tgz", - "integrity": "sha512-d6YmIWdYvwk6WaknHRcJgiXeJvX9K5i8uPMAaL2P2/LU8n3moIQ59C7SP0uULcHuuiREEmFWOyyrWnGxZCI9bg==", - "dev": true, - "dependencies": { - "@angular-devkit/core": "13.3.4", - "rxjs": "6.6.7" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { - "version": "13.3.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.4.tgz", - "integrity": "sha512-gj6i8ksPaT2bvYwI7wKJxLX53pHfTmZc1RaNbAGfZB1/zFNnb3MPj8utTcJSk4qMsGXuDDhiB7hpTKBw8ROaGA==", - "dev": true, - "dependencies": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - }, - "engines": { - "node": "^12.20.0 || ^14.15.0 || >=16.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, "node_modules/@angular-devkit/build-webpack/node_modules/rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -341,9 +257,9 @@ "dev": true }, "node_modules/@angular-devkit/core": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.3.tgz", - "integrity": "sha512-lfQwY9LuVRwcNVzGmyPcwOpb3CAobP4T+c3joR1LLIPS5lzcM0oeCE2bon9N52Ktn4Q/pH98dVtjWL+jSrUADw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.4.tgz", + "integrity": "sha512-gj6i8ksPaT2bvYwI7wKJxLX53pHfTmZc1RaNbAGfZB1/zFNnb3MPj8utTcJSk4qMsGXuDDhiB7hpTKBw8ROaGA==", "dev": true, "dependencies": { "ajv": "8.9.0", @@ -386,12 +302,12 @@ "dev": true }, "node_modules/@angular-devkit/schematics": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.3.tgz", - "integrity": "sha512-S8UNlw6IoR/kxBYbiwesuA7oJGSnFkD6bJwVLhpHdT6Sqrz2/IrjHcNgTJRAvhsOKIbfDtMtXRzl/PUdWEfgyw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.4.tgz", + "integrity": "sha512-gKNpMMoZJjLKdXxjuVembic4GWa4AYV7kU1ou3ZuZoDKtKcig9URISr1wjS+nrhKYz+miFy0zIqSGMMattDlDQ==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.3.3", + "@angular-devkit/core": "13.3.4", "jsonc-parser": "3.0.0", "magic-string": "0.25.7", "ora": "5.4.1", @@ -458,16 +374,16 @@ "optional": true }, "node_modules/@angular/cli": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.3.tgz", - "integrity": "sha512-a+nnzFP1FfnypXpAhrHbIBaJcxzegWLZUvVzJQwt6P2z60IoHdvTVmyNbY89qI0LE1SrAokEUO1zW3Yjmu7fUw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.4.tgz", + "integrity": "sha512-4S5FNjkZgq98zcBVgwkYtMgMRMSVsprCgq7dM8yTxIQh+Np3fYgj5eRJ1+mfFG/kankH2z/TFyuoYiILh2D9Uw==", "dev": true, "hasInstallScript": true, "dependencies": { - "@angular-devkit/architect": "0.1303.3", - "@angular-devkit/core": "13.3.3", - "@angular-devkit/schematics": "13.3.3", - "@schematics/angular": "13.3.3", + "@angular-devkit/architect": "0.1303.4", + "@angular-devkit/core": "13.3.4", + "@angular-devkit/schematics": "13.3.4", + "@schematics/angular": "13.3.4", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -2655,13 +2571,13 @@ } }, "node_modules/@schematics/angular": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.3.tgz", - "integrity": "sha512-kX5ghVCmWHcMN+g0pUaFuIJzwrXsVnK4bfid8DckU4EEtfFSv3UA5I1QNJRgpCPxTPhNEAk+3ePN8nzDSjdU+w==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.4.tgz", + "integrity": "sha512-Cta11k965Igz2kWj60KQ/9z6RFAg9FjZ8i1TH4nyROJs9nWemWPQNA+OJFuXrEy6Ldpk7yJ5cWgJsyryGB25PA==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.3.3", - "@angular-devkit/schematics": "13.3.3", + "@angular-devkit/core": "13.3.4", + "@angular-devkit/schematics": "13.3.4", "jsonc-parser": "3.0.0" }, "engines": { @@ -11711,12 +11627,12 @@ } }, "@angular-devkit/architect": { - "version": "0.1303.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.3.tgz", - "integrity": "sha512-WRVVBCzLlMqRZVhZXGASHzNJK/OCAvl/DTGhlLuJDIjF7lVGnXHjtwNM8ilYZq949OnC3fly5Z61TfhbN/OHCg==", + "version": "0.1303.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.4.tgz", + "integrity": "sha512-d6YmIWdYvwk6WaknHRcJgiXeJvX9K5i8uPMAaL2P2/LU8n3moIQ59C7SP0uULcHuuiREEmFWOyyrWnGxZCI9bg==", "dev": true, "requires": { - "@angular-devkit/core": "13.3.3", + "@angular-devkit/core": "13.3.4", "rxjs": "6.6.7" }, "dependencies": { @@ -11810,30 +11726,6 @@ "webpack-subresource-integrity": "5.1.0" }, "dependencies": { - "@angular-devkit/architect": { - "version": "0.1303.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.4.tgz", - "integrity": "sha512-d6YmIWdYvwk6WaknHRcJgiXeJvX9K5i8uPMAaL2P2/LU8n3moIQ59C7SP0uULcHuuiREEmFWOyyrWnGxZCI9bg==", - "dev": true, - "requires": { - "@angular-devkit/core": "13.3.4", - "rxjs": "6.6.7" - } - }, - "@angular-devkit/core": { - "version": "13.3.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.4.tgz", - "integrity": "sha512-gj6i8ksPaT2bvYwI7wKJxLX53pHfTmZc1RaNbAGfZB1/zFNnb3MPj8utTcJSk4qMsGXuDDhiB7hpTKBw8ROaGA==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - } - }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -11869,30 +11761,6 @@ "rxjs": "6.6.7" }, "dependencies": { - "@angular-devkit/architect": { - "version": "0.1303.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.4.tgz", - "integrity": "sha512-d6YmIWdYvwk6WaknHRcJgiXeJvX9K5i8uPMAaL2P2/LU8n3moIQ59C7SP0uULcHuuiREEmFWOyyrWnGxZCI9bg==", - "dev": true, - "requires": { - "@angular-devkit/core": "13.3.4", - "rxjs": "6.6.7" - } - }, - "@angular-devkit/core": { - "version": "13.3.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.4.tgz", - "integrity": "sha512-gj6i8ksPaT2bvYwI7wKJxLX53pHfTmZc1RaNbAGfZB1/zFNnb3MPj8utTcJSk4qMsGXuDDhiB7hpTKBw8ROaGA==", - "dev": true, - "requires": { - "ajv": "8.9.0", - "ajv-formats": "2.1.1", - "fast-json-stable-stringify": "2.1.0", - "magic-string": "0.25.7", - "rxjs": "6.6.7", - "source-map": "0.7.3" - } - }, "rxjs": { "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", @@ -11911,9 +11779,9 @@ } }, "@angular-devkit/core": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.3.tgz", - "integrity": "sha512-lfQwY9LuVRwcNVzGmyPcwOpb3CAobP4T+c3joR1LLIPS5lzcM0oeCE2bon9N52Ktn4Q/pH98dVtjWL+jSrUADw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.4.tgz", + "integrity": "sha512-gj6i8ksPaT2bvYwI7wKJxLX53pHfTmZc1RaNbAGfZB1/zFNnb3MPj8utTcJSk4qMsGXuDDhiB7hpTKBw8ROaGA==", "dev": true, "requires": { "ajv": "8.9.0", @@ -11942,12 +11810,12 @@ } }, "@angular-devkit/schematics": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.3.tgz", - "integrity": "sha512-S8UNlw6IoR/kxBYbiwesuA7oJGSnFkD6bJwVLhpHdT6Sqrz2/IrjHcNgTJRAvhsOKIbfDtMtXRzl/PUdWEfgyw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.4.tgz", + "integrity": "sha512-gKNpMMoZJjLKdXxjuVembic4GWa4AYV7kU1ou3ZuZoDKtKcig9URISr1wjS+nrhKYz+miFy0zIqSGMMattDlDQ==", "dev": true, "requires": { - "@angular-devkit/core": "13.3.3", + "@angular-devkit/core": "13.3.4", "jsonc-parser": "3.0.0", "magic-string": "0.25.7", "ora": "5.4.1", @@ -11997,15 +11865,15 @@ } }, "@angular/cli": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.3.tgz", - "integrity": "sha512-a+nnzFP1FfnypXpAhrHbIBaJcxzegWLZUvVzJQwt6P2z60IoHdvTVmyNbY89qI0LE1SrAokEUO1zW3Yjmu7fUw==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.4.tgz", + "integrity": "sha512-4S5FNjkZgq98zcBVgwkYtMgMRMSVsprCgq7dM8yTxIQh+Np3fYgj5eRJ1+mfFG/kankH2z/TFyuoYiILh2D9Uw==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1303.3", - "@angular-devkit/core": "13.3.3", - "@angular-devkit/schematics": "13.3.3", - "@schematics/angular": "13.3.3", + "@angular-devkit/architect": "0.1303.4", + "@angular-devkit/core": "13.3.4", + "@angular-devkit/schematics": "13.3.4", + "@schematics/angular": "13.3.4", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -13539,13 +13407,13 @@ "peer": true }, "@schematics/angular": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.3.tgz", - "integrity": "sha512-kX5ghVCmWHcMN+g0pUaFuIJzwrXsVnK4bfid8DckU4EEtfFSv3UA5I1QNJRgpCPxTPhNEAk+3ePN8nzDSjdU+w==", + "version": "13.3.4", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.4.tgz", + "integrity": "sha512-Cta11k965Igz2kWj60KQ/9z6RFAg9FjZ8i1TH4nyROJs9nWemWPQNA+OJFuXrEy6Ldpk7yJ5cWgJsyryGB25PA==", "dev": true, "requires": { - "@angular-devkit/core": "13.3.3", - "@angular-devkit/schematics": "13.3.3", + "@angular-devkit/core": "13.3.4", + "@angular-devkit/schematics": "13.3.4", "jsonc-parser": "3.0.0" } }, diff --git a/frontend/package.json b/frontend/package.json index a56f100..a925267 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,7 +28,7 @@ }, "devDependencies": { "@angular-devkit/build-angular": "~13.3.4", - "@angular/cli": "~13.3.3", + "@angular/cli": "~13.3.4", "@angular/compiler-cli": "~13.2.0", "@types/jasmine": "~4.0.3", "@types/node": "^17.0.29",