Commit cdc03ca0 authored by Manuel's avatar Manuel

added CRUD endpoints to microservice

parent 1f634d10
...@@ -4,6 +4,10 @@ info: ...@@ -4,6 +4,10 @@ info:
description: This is the documentation for the business logic microservice. description: This is the documentation for the business logic microservice.
version: "1.0.0" version: "1.0.0"
# Import security definitions from seperate file
securityDefinitions:
$ref: '../security/security.yml#securityDefinitions'
consumes: consumes:
- "application/json" - "application/json"
produces: produces:
...@@ -12,8 +16,243 @@ produces: ...@@ -12,8 +16,243 @@ produces:
basePath: "/api" basePath: "/api"
paths: paths:
/layers/{name}/cluster-mapping:
delete:
security:
- JwtAdmin: []
operationId: "routes.layer.delete_cluster_mapping"
tags:
- "Layers"
summary: "Deletes a cluster-attribute mapping from the selected layer"
description: "Deletes a cluster-attribute mapping from the selected layer"
parameters:
- name: "name"
in: "path"
description: "Name of the layer (must exist)"
required: true
type: "string"
- in: body
name: "Object"
required: true
schema:
type: object
properties:
attribute:
type: string
example: "end_time"
description: "Internal name of the attribute"
responses:
'200':
description: "Successful Request"
'404':
description: "Layer does not exist"
'400':
description: "Field in request is missing or attribute does not exist in the Layer"
put:
security:
- JwtAdmin: []
operationId: "routes.layer.add_cluster_mapping"
tags:
- "Layers"
summary: "Adds a cluster-attribute mapping to the selected layer"
description: "Adds a cluster-attribute mapping to the selected layer"
parameters:
- name: "name"
in: "path"
description: "Name of the layer (must exist)"
required: true
type: "string"
- in: body
name: "Object"
required: true
schema:
type: object
properties:
attribute:
type: string
example: "end_time"
description: "Internal name of the attribute"
responses:
'200':
description: "Successful Request"
'404':
description: "Layer does not exist"
'400':
description: "Field in request is missing or attribute does not exist in the Layer"
/layers/{name}/mapping:
delete:
security:
- JwtAdmin: []
operationId: "routes.layer.delete_mapping"
tags:
- "Layers"
summary: "Deletes an attribute mapping from the selected layer"
description: "Deletes an attribute mapping from the selected layer"
parameters:
- name: "name"
in: "path"
description: "Name of the layer (must exist)"
required: true
type: "string"
- in: body
name: "Object"
required: true
schema:
type: object
properties:
internal:
type: string
example: "end_time"
responses:
'200':
description: "Successful Request"
'404':
description: "Layer does not exist"
'400':
description: "Field in request is missing"
put:
security:
- JwtAdmin: []
operationId: "routes.layer.add_mapping"
tags:
- "Layers"
summary: "Adds an attribute mapping to the selected layer"
description: "Adds an attribute mapping to the selected layer"
parameters:
- name: "name"
in: "path"
description: "Name of the layer (must exist)"
required: true
type: "string"
- in: body
name: "Object"
required: true
schema:
type: object
properties:
internal:
type: string
example: "end_time"
external:
type: string
example: "arrival_unix_timestamp_utc"
responses:
'200':
description: "Successful Request"
'404':
description: "Layer does not exist"
'400':
description: "Field in request is missing"
/layers/{name}:
delete:
security:
- JwtAdmin: []
operationId: "routes.layer.delete_one"
tags:
- "Layers"
summary: "Delete one LayerAdapter from the DB"
description: "Delete one LayerAdapter from the DB"
parameters:
- name: "name"
in: "path"
description: "Name of the layer to delete"
required: true
type: "string"
responses:
'200':
description: "Successful Request"
'404':
description: "Layer does not exist"
get:
security:
- JwtAdmin: []
operationId: "routes.layer.one"
tags:
- "Layers"
summary: "Retrieve one LayerAdapter from the DB"
description: "Retrieve one LayerAdapter from the DB"
parameters:
- name: "name"
in: "path"
description: "Name of the layer"
required: true
type: "string"
responses:
'200':
description: "Successful Request"
'404':
description: "Layer does not exist"
post:
security:
- JwtAdmin: []
operationId: "routes.layer.add"
tags:
- "Layers"
summary: "Adds a new layer to the DB"
description: "Adds a new layer to the DB"
parameters:
- name: "name"
in: "path"
description: "Name of the new layer"
required: true
type: "string"
responses:
'200':
description: "Successful Request"
'400':
description: "Layer already exists"
/layers:
post:
security:
- JwtAdmin: []
operationId: "routes.layer.add_complete"
tags:
- "Layers"
summary: "Adds a complete layer (including its properties) to the DB"
description: "Adds a complete layer (including its properties) to the DB"
parameters:
- in: body
name: "Object"
required: true
schema:
type: object
properties:
name:
type: string
example: "layer1"
cluster_properties:
type: array
items:
type: string
example: "internal_property_1"
properties:
type: object
additionalProperties:
type: string
example:
"internal_property_1": "external_property_1"
"internal_property_2": "external_property_2"
responses:
'200':
description: "Successful Request"
'400':
description: "Bad structure in request body or layer already exists"
get:
security:
- JwtAdmin: []
operationId: "routes.layer.all"
tags:
- "Layers"
summary: "Retrieve all LayerAdapters from the DB"
description: "Retrieve all LayerAdapters from the DB"
responses:
'200':
description: "Successful Request"
/debug: /debug:
post: post:
security:
- JwtAdmin: []
operationId: "debug.echo" operationId: "debug.echo"
tags: tags:
- "Echo" - "Echo"
...@@ -26,5 +265,5 @@ paths: ...@@ -26,5 +265,5 @@ paths:
schema: schema:
type: object type: object
responses: responses:
200: '200':
description: "Successful echo of request data" description: "Successful echo of request data"
\ No newline at end of file
swagger: "2.0"
info:
title: Business Logic microservice
description: This is the documentation for the business logic microservice.
version: "1.0.0"
consumes:
- "application/json"
produces:
- "application/json"
basePath: "/api"
paths:
/layers/{name}/cluster-mapping:
delete:
operationId: "routes.layer.delete_cluster_mapping"
tags:
- "Layers"
summary: "Deletes a cluster-attribute mapping from the selected layer"
description: "Deletes a cluster-attribute mapping from the selected layer"
parameters:
- name: "name"
in: "path"
description: "Name of the layer (must exist)"
required: true
type: "string"
- in: body
name: "Object"
required: true
schema:
type: object
properties:
attribute:
type: string
example: "end_time"
description: "Internal name of the attribute"
responses:
'200':
description: "Successful Request"
'404':
description: "Layer does not exist"
'400':
description: "Field in request is missing or attribute does not exist in the Layer"
put:
operationId: "routes.layer.add_cluster_mapping"
tags:
- "Layers"
summary: "Adds a cluster-attribute mapping to the selected layer"
description: "Adds a cluster-attribute mapping to the selected layer"
parameters:
- name: "name"
in: "path"
description: "Name of the layer (must exist)"
required: true
type: "string"
- in: body
name: "Object"
required: true
schema:
type: object
properties:
attribute:
type: string
example: "end_time"
description: "Internal name of the attribute"
responses:
'200':
description: "Successful Request"
'404':
description: "Layer does not exist"
'400':
description: "Field in request is missing or attribute does not exist in the Layer"
/layers/{name}/mapping:
delete:
operationId: "routes.layer.delete_mapping"
tags:
- "Layers"
summary: "Deletes an attribute mapping from the selected layer"
description: "Deletes an attribute mapping from the selected layer"
parameters:
- name: "name"
in: "path"
description: "Name of the layer (must exist)"
required: true
type: "string"
- in: body
name: "Object"
required: true
schema:
type: object
properties:
internal:
type: string
example: "end_time"
responses:
'200':
description: "Successful Request"
'404':
description: "Layer does not exist"
'400':
description: "Field in request is missing"
put:
operationId: "routes.layer.add_mapping"
tags:
- "Layers"
summary: "Adds an attribute mapping to the selected layer"
description: "Adds an attribute mapping to the selected layer"
parameters:
- name: "name"
in: "path"
description: "Name of the layer (must exist)"
required: true
type: "string"
- in: body
name: "Object"
required: true
schema:
type: object
properties:
internal:
type: string
example: "end_time"
external:
type: string
example: "arrival_unix_timestamp_utc"
responses:
'200':
description: "Successful Request"
'404':
description: "Layer does not exist"
'400':
description: "Field in request is missing"
/layers/{name}:
delete:
operationId: "routes.layer.delete_one"
tags:
- "Layers"
summary: "Delete one LayerAdapter from the DB"
description: "Delete one LayerAdapter from the DB"
parameters:
- name: "name"
in: "path"
description: "Name of the layer to delete"
required: true
type: "string"
responses:
'200':
description: "Successful Request"
'404':
description: "Layer does not exist"
get:
operationId: "routes.layer.one"
tags:
- "Layers"
summary: "Retrieve one LayerAdapter from the DB"
description: "Retrieve one LayerAdapter from the DB"
parameters:
- name: "name"
in: "path"
description: "Name of the layer"
required: true
type: "string"
responses:
'200':
description: "Successful Request"
'404':
description: "Layer does not exist"
post:
operationId: "routes.layer.add"
tags:
- "Layers"
summary: "Adds a new layer to the DB"
description: "Adds a new layer to the DB"
parameters:
- name: "name"
in: "path"
description: "Name of the new layer"
required: true
type: "string"
responses:
'200':
description: "Successful Request"
'400':
description: "Layer already exists"
/layers:
post:
operationId: "routes.layer.add_complete"
tags:
- "Layers"
summary: "Adds a complete layer (including its properties) to the DB"
description: "Adds a complete layer (including its properties) to the DB"
parameters:
- in: body
name: "Object"
required: true
schema:
type: object
properties:
name:
type: string
example: "layer1"
cluster_properties:
type: array
items:
type: string
example: "internal_property_1"
properties:
type: object
additionalProperties:
type: string
example:
"internal_property_1": "external_property_1"
"internal_property_2": "external_property_2"
responses:
'200':
description: "Successful Request"
'400':
description: "Bad structure in request body or layer already exists"
get:
operationId: "routes.layer.all"
tags:
- "Layers"
summary: "Retrieve all LayerAdapters from the DB"
description: "Retrieve all LayerAdapters from the DB"
responses:
'200':
description: "Successful Request"
/debug:
post:
operationId: "debug.echo"
tags:
- "Echo"
summary: "Echo function for debugging purposes"
description: "Echoes the input back to the caller."
parameters:
- in: body
name: "Object"
required: true
schema:
type: object
responses:
'200':
description: "Successful echo of request data"
from typing import Dict, List
class LayerAdapter:
'''
represents the mapping from an internal layer with a set of attributes and what
attributes from the dataset correspond to each one
'''
def __init__(self, name: str, properties: Dict[str, str], cluster_properties: List[str]):
'''
Creates a new instance of LayerAdapter
@params:
name - Required : unique identifier for the layer
properties - Required : maps InternalPropertyName->DatasetPropertyName
cluster_properties - Required : subset of the keys of properties, marks properties to cluster with
'''
self.name = name
self.properties = properties
for prop in cluster_properties:
if prop not in properties.keys():
raise ValueError(f"{prop} is no property in the layer!")
self.cluster_properties = cluster_properties
def add_mapping(self, internal: str, external: str):
'''
Add a new mapping to the layer. This mapping consists of an internal representation.
@params:
internal - Required: string identifier used internally, f.e. "startTime"
external - Required: string identifier the column has in the external dataset f.e. "arrivalTimeOfCustomer"
'''
self.properties[internal] = external
def delete_mapping(self, internal: str):
'''
Removes a mapping from the layer. Raises a ValueError if the mapping identified by the internal representation
does not exist.
@params:
internal - Required: string identifier used internally, f.e. "startTime"
'''
if internal not in self.properties.keys():
raise ValueError(f"Attribute {internal} is not an internal attribute!")
if internal in self.cluster_properties:
self.delete_cluster_mapping(internal)
del self.properties[internal]
def add_cluster_mapping(self, attribute:str):
'''
Adds an attribute of the internal representations to the set of attributes to cluster after. The set of cluster
attribute is always a subset of self.properties.keys().
@params:
attribute - Required: string identifier used internally, f.e. "startTime"
'''
if attribute not in self.properties.keys():
raise ValueError(f"Attribute {attribute} is not an internal attribute!")
if attribute not in self.cluster_properties:
self.cluster_properties.append(attribute)
def delete_cluster_mapping(self, attribute:str):
'''
Removes an attribute from the set of cluster-attributes. Raises a ValueError if either the attribute does not exist
as an internal representation or if the attribute exists, but was not added as a cluster-attribute yet
@params:
attribute - Required: string identifier used internally, f.e. "startTime"
'''
if attribute not in self.properties.keys():
raise ValueError(f"Attribute {attribute} is not an internal attribute!")
self.cluster_properties.remove(attribute)
def to_serializable_dict(self) -> Dict:
return {
"name": self.name,
"properties": self.properties,
"cluster_properties": self.cluster_properties
}
@staticmethod
def from_serializable_dict(user_dict: Dict):
return LayerAdapter(
user_dict["name"],
user_dict["properties"],
user_dict["cluster_properties"]
)
# global imports (dont't worry, red is normal)
import network_constants as netconst
from database.MongoRepositoryBase import MongoRepositoryBase
from db.entities.layer_adapter import LayerAdapter
import pymongo
import json
from typing import List
class Repository(MongoRepositoryBase):
'''This is a repository for MongoDb.'''
def __init__(self):
super().__init__(netconst.BUSINESS_LOGIC_DB_HOSTNAME,
netconst.BUSINESS_LOGIC_DB_PORT,
'rest-gateway-db')
self._adapter_collection = 'layer_adapters'
def add(self, adapter : LayerAdapter):
super().insert_entry(self._adapter_collection, adapter.to_serializable_dict())
def one_by_name(self, name : str) -> LayerAdapter:
return list(super().get_entries(self._adapter_collection, selection={"name": name}))
def update(self, adapter : LayerAdapter):
collection = self._database[self._adapter_collection]
collection.update_one({"name":adapter.name}, {"$set": adapter.to_serializable_dict()})
def delete_all_with_name(self, name: str):
collection = self._database[self._adapter_collection]
collection.delete_many({"name": name})
def all(self) -> List[LayerAdapter]:
result = super().get_entries(self._adapter_collection, projection={'_id': False})
return list(result)
\ No newline at end of file
import connexion # add modules folder to interpreter path
import sys
import os
from pathlib import Path
from typing import Dict, Any
modules_path = '../../../modules/'
if os.path.exists(modules_path):
sys.path.insert(1, modules_path)
# load swagger config # load swagger config
import connexion
from security import swagger_util
app = connexion.App(__name__, specification_dir='configs/') app = connexion.App(__name__, specification_dir='configs/')
app.add_api('swagger.yml')
from db.entities.layer_adapter import LayerAdapter
@app.route('/', methods=['GET']) @app.route('/', methods=['GET'])
def api_root(): def api_root():
return 'Endpoint of business-logic-microservice!' return 'Endpoint of business-logic-microservice!'
# SSL configuration
try:
certificate_path = os.environ['ARTICONF_CERTIFICATE_PATH']
except KeyError:
certificate_path = '/srv/articonf/'
context = (os.path.normpath(f'{certificate_path}/articonf1.crt'), os.path.normpath(f'{certificate_path}/articonf1.key')) # certificate and key files
# Local Mode
try:
print("Running with local settings...")
local = os.environ['ARTICONF_LOCAL']
app.add_api(swagger_util.get_bundled_specs(Path("configs/swagger_local.yml")),
resolver = connexion.RestyResolver("cms_rest_api"))
except KeyError:
app.add_api(swagger_util.get_bundled_specs(Path("configs/swagger.yml")),
resolver = connexion.RestyResolver("cms_rest_api"))
# start app # start app
if __name__ == '__main__': if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True) app.run(host='0.0.0.0', port=5000, debug=False, ssl_context=context)
#global imports
from db.entities.layer_adapter import LayerAdapter
from services.layer_adapter_service import LayerAdapterService
import json
from flask import Response, request
def all():
return LayerAdapterService.all()
def add(name:str):
'''
add an empty layer to the DB
@params:
name - Required : unique identifier for the layer
'''
if LayerAdapterService.one(name) != None:
return Response(status=400, response=f"Layer with name '{name}' already exists!")
LayerAdapterService.add(name)
return Response(status=200)
def add_complete():
'''
add a layer already containing attribute mappings to the BD.
'''
data = request.json
if "name" not in data or "properties" not in data or "cluster_properties" not in data:
return Response(status=400, response=f"Field missing! Fields required: (name, properties, cluster_properties)")
layer = LayerAdapterService.one(data["name"])
if layer != None:
return Response(status=400, response=f'Layer with name "{data["name"]}" already exists!')
try:
layer_new = LayerAdapter.from_serializable_dict(data)
LayerAdapterService.add_complete(layer_new)
return Response(status=200)
except:
return Response(status=400)
def one(name:str):
'''
fetch a single layer from the DB
@params:
name - Required : unique identifier for the layer
'''
layer = LayerAdapterService.one(name)
if layer == None:
return Response(status=404, response=f"Layer with name '{name}' does not exist!")
return Response(status=200, response=json.dumps(layer.to_serializable_dict()))
def add_mapping(name:str):
'''
add a new mapping to the layer identified by name
@params:
name - Required : unique identifier for the layer
'''
layer = LayerAdapterService.one(name)
if layer == None:
return Response(status=404, response=f"Layer with name '{name}' does not exist!")
data = request.json
if "internal" not in data or "external" not in data:
return Response(status=400, response=f"Field missing! Fields required: (internal, external)")
layer.add_mapping(data["internal"], data["external"])
LayerAdapterService.update(layer)
return Response(status=200)
def delete_mapping(name:str):
'''
delete a mapping from the layer identified by name
@params:
name - Required : unique identifier for the layer
'''
layer = LayerAdapterService.one(name)
if layer == None:
return Response(status=404, response=f"Layer with name '{name}' does not exist!")
data = request.json
if "internal" not in data:
return Response(status=400, response=f"Field missing! Fields required: (internal)")
try:
layer.delete_mapping(data["internal"])
LayerAdapterService.update(layer)
except ValueError:
return Response(status=400, response=f'{data["internal"]} is not a property of the layer!')
return Response(status=200)
def add_cluster_mapping(name:str):
'''
add a mapped property to the list of properties to cluster with
@params:
name - Required : unique identifier for the layer
'''
layer = LayerAdapterService.one(name)
if layer == None:
return Response(status=404, response=f"Layer with name '{name}' does not exist!")
data = request.json
if "attribute" not in data:
return Response(status=400, response=f"Field missing! Fields required: (attribute)")
try:
layer.add_cluster_mapping(data["attribute"])
LayerAdapterService.update(layer)
return Response(status=200)
except:
return Response(status=400, response=f'{data["attribute"]} is no attribute in the layer!')
def delete_cluster_mapping(name:str):
'''
remove a mapped property from the list of properties to cluster with
@params:
name - Required : unique identifier for the layer
'''
layer = LayerAdapterService.one(name)
if layer == None:
return Response(status=404, response=f"Layer with name '{name}' does not exist!")
data = request.json
if "attribute" not in data:
return Response(status=400, response=f"Field missing! Fields required: (attribute)")
try:
layer.delete_cluster_mapping(data["attribute"])
LayerAdapterService.update(layer)
return Response(status=200)
except ValueError as e:
print(e)
return Response(status=400, response=f'{data["attribute"]} is no attribute in the layer!')
def delete_one(name:str):
'''
delete a layer and all its mappings from the Db
@params:
name - Required : unique identifier for the layer
'''
layer = LayerAdapterService.one(name)
if layer == None:
return Response(status=404, response=f"Layer with name '{name}' does not exist!")
LayerAdapterService.delete(layer)
return Response(status=200)
\ No newline at end of file
#global imports
from db.repository import Repository
from db.entities.layer_adapter import LayerAdapter
from typing import List
class LayerAdapterService:
_repository = Repository()
@staticmethod
def all() -> List[LayerAdapter]:
'''
Return all currently defined layers
'''
return LayerAdapterService._repository.all()
@staticmethod
def update(layer: LayerAdapter):
'''
Overwrite the stored instance with the given one which is
identified by the layer name
@params:
layer - Required : layer object holding the current data
'''
LayerAdapterService._repository.update(layer)
@staticmethod
def add(name: str):
'''
Add a new layer to the DB. Attribute mapping and cluster
attributes will be empty per default
@params:
name - Required : Unique name for a layer.
'''
adapter_new = LayerAdapter(name, {}, [])
LayerAdapterService._repository.add(adapter_new)
@staticmethod
def add_complete(layer: LayerAdapter):
'''
Add a new layer to the DB. Attribute mappings and cluster
attributes of the given layer are used
@params:
layer - Required : layer object holding correct data
'''
LayerAdapterService._repository.add(layer)
@staticmethod
def delete(layer: LayerAdapter):
'''
delete all layers with the given name.
@params:
layer - Required : layer object to remove from the DB
'''
LayerAdapterService._repository.delete_all_with_name(layer.name)
@staticmethod
def one(name: str) -> LayerAdapter:
'''
Retrieve a single layer from the DB. Returns None if no layer
was found under this name.
@params:
name - Required : Unique name for a layer.
'''
result = LayerAdapterService._repository.one_by_name(name)
if len(result) == 1:
return LayerAdapter.from_serializable_dict(result[0])
else:
return None
import unittest
from db.entities.layer_adapter import LayerAdapter
class Layer_Adapter_Test(unittest.TestCase):
def test_valid_adapter(self):
adapter1 = LayerAdapter("layer1", {"a":"b", "c":"d"}, ["a"])
print(adapter1.to_serializable_dict)
if __name__ == '__main__':
unittest.main()
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment