ApiLogicServer 15.0.37__py3-none-any.whl → 15.0.40__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- api_logic_server_cli/api_logic_server.py +2 -2
- api_logic_server_cli/api_logic_server_info.yaml +2 -2
- api_logic_server_cli/genai/genai_svcs.py +4 -3
- api_logic_server_cli/prototypes/base/.copilot-instructions.md +10 -0
- api_logic_server_cli/prototypes/base/.vscode/.copilot-instructions.md +178 -0
- api_logic_server_cli/prototypes/base/README_PROJECT.md +43 -0
- api_logic_server_cli/prototypes/base/api_logic_server_run.py +20 -8
- api_logic_server_cli/prototypes/base/docs/training/react_map.prompt.md +13 -0
- api_logic_server_cli/prototypes/base/docs/training/react_tree.prompt.md +10 -0
- api_logic_server_cli/prototypes/base/integration/mcp/readme-mcp.md +9 -0
- api_logic_server_cli/prototypes/base/logic/logic_discovery/readme_logic_discovery.md +9 -0
- api_logic_server_cli/prototypes/base/logic/logic_discovery/use_case.py +27 -0
- api_logic_server_cli/prototypes/base/logic/{readme_declare_logic.md → readme_logic.md} +70 -1
- api_logic_server_cli/prototypes/base/readme.md +30 -5
- api_logic_server_cli/prototypes/base/security/readme_security.md +17 -0
- api_logic_server_cli/prototypes/manager/.copilot-instructions.md +13 -0
- api_logic_server_cli/prototypes/manager/.vscode/.copilot-instructions.md +58 -0
- api_logic_server_cli/prototypes/manager/.vscode/ApiLogicServer.code-workspace +2 -2
- api_logic_server_cli/prototypes/manager/README.md +13 -1
- api_logic_server_cli/prototypes/manager/system/Manager_workspace.code-workspace +3 -3
- api_logic_server_cli/prototypes/nw/api/api_discovery/authentication_expose_api_models.py +53 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/auto_discovery.py +27 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/count_orders_by_month.html +76 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/count_orders_by_month.sql +1 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/dashboard_services.py +143 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/mcp_discovery.py +97 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/new_service.py +21 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/newer_service.py +21 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/number_of_sales_per_category.html +76 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/number_of_sales_per_category.sql +1 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/ontimize_api.py +495 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/sales_by_category.html +76 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/sales_by_category.sql +1 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/system.py +77 -0
- api_logic_server_cli/prototypes/nw/database/database_discovery/graphics_services.py +173 -0
- api_logic_server_cli/prototypes/nw/ui/admin/home.js +5 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/DEPARTMENT_TREE_VIEW.md +66 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/README.md +21 -4
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/package.json +4 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/public/index.html +3 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/App.js +8 -1
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/CustomLayout.js +20 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Department.js +511 -24
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/DepartmentTree.js +147 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Employee.js +230 -18
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/LandingPage.js +264 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Supplier.js +359 -121
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/index.js +1 -0
- {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.40.dist-info}/METADATA +1 -1
- {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.40.dist-info}/RECORD +54 -25
- api_logic_server_cli/prototypes/base/docs/training/admin_app_unused.md +0 -156
- {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.40.dist-info}/WHEEL +0 -0
- {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.40.dist-info}/entry_points.txt +0 -0
- {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.40.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-15.0.37.dist-info → apilogicserver-15.0.40.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import safrs
|
|
3
|
+
import importlib
|
|
4
|
+
import pathlib
|
|
5
|
+
import logging as logging
|
|
6
|
+
import flask_sqlalchemy
|
|
7
|
+
|
|
8
|
+
# use absolute path import for easier multi-{app,model,db} support
|
|
9
|
+
force_import = __import__("database.database_discovery.authentication_models")
|
|
10
|
+
database = __import__('database')
|
|
11
|
+
app_logger = logging.getLogger(__name__)
|
|
12
|
+
app_logger.debug("api/expose_api_models.py - endpoint for each table")
|
|
13
|
+
|
|
14
|
+
def add_check_sum(cls):
|
|
15
|
+
"""
|
|
16
|
+
Checksum decorator for each model
|
|
17
|
+
"""
|
|
18
|
+
@safrs.jsonapi_attr
|
|
19
|
+
def _check_sum_(self):
|
|
20
|
+
if isinstance(self, flask_sqlalchemy.model.DefaultMeta) or not hasattr(self, "_check_sum_property"): # property does not exist during initialization
|
|
21
|
+
return None
|
|
22
|
+
return self._check_sum_property
|
|
23
|
+
|
|
24
|
+
@_check_sum_.setter
|
|
25
|
+
def _check_sum_(self, value):
|
|
26
|
+
self._check_sum_property = value
|
|
27
|
+
|
|
28
|
+
cls.S_CheckSum = _check_sum_
|
|
29
|
+
return cls
|
|
30
|
+
|
|
31
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators ):
|
|
32
|
+
""" Called by api_discovery to
|
|
33
|
+
* expose API for each model (using introspection)
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
app (_type_): _description_
|
|
37
|
+
api (_type_): _description_
|
|
38
|
+
project_dir (_type_): _description_
|
|
39
|
+
swagger_host (str): _description_
|
|
40
|
+
PORT (str): _description_
|
|
41
|
+
method_decorators (_type_): _description_
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
_type_: _description_
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
# Get all the subclasses of the Base class and expose them in the api
|
|
48
|
+
for name, obj in inspect.getmembers(database.database_discovery.authentication_models):
|
|
49
|
+
if inspect.isclass(obj) and issubclass(obj, database.models.SAFRSBaseX) and obj is not database.models.SAFRSBaseX:
|
|
50
|
+
app_logger.info(f"Exposing /{name}")
|
|
51
|
+
api.expose_object(add_check_sum(obj), method_decorators= method_decorators)
|
|
52
|
+
|
|
53
|
+
return api
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
app_logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
def discover_services(app, api, project_dir, swagger_host: str, PORT: str):
|
|
8
|
+
"""
|
|
9
|
+
Discover services in the services directory
|
|
10
|
+
"""
|
|
11
|
+
import os
|
|
12
|
+
method_decorators : list = []
|
|
13
|
+
services = []
|
|
14
|
+
services_path = Path(__file__).parent
|
|
15
|
+
for root, dirs, files in os.walk(services_path):
|
|
16
|
+
for file in files:
|
|
17
|
+
if file.endswith(".py"):
|
|
18
|
+
spec = importlib.util.spec_from_file_location("module.name", services_path.joinpath(file))
|
|
19
|
+
if file.endswith("auto_discovery.py"):
|
|
20
|
+
pass
|
|
21
|
+
else:
|
|
22
|
+
services.append(file)
|
|
23
|
+
each_service = importlib.util.module_from_spec(spec)
|
|
24
|
+
spec.loader.exec_module(each_service) # runs "bare" module code (e.g., initialization)
|
|
25
|
+
each_service.add_service(app, api, project_dir, swagger_host, PORT, method_decorators) # invoke create function
|
|
26
|
+
app.logger.info(f"..discovered services: {services}")
|
|
27
|
+
return
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Bar Chart Example</title>
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<canvas id="myChart" width="400" height="200"></canvas>
|
|
11
|
+
<script>
|
|
12
|
+
// Input data
|
|
13
|
+
fetch('http://localhost:5656/api/GraphicsServices/count_orders_by_month', {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: {
|
|
16
|
+
'accept': 'application/vnd.api+json',
|
|
17
|
+
'Content-Type': 'application/json'
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify({}) // Add any required payload here
|
|
20
|
+
})
|
|
21
|
+
.then(response => {
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
24
|
+
}
|
|
25
|
+
return response.json();
|
|
26
|
+
})
|
|
27
|
+
.then(result => {
|
|
28
|
+
const labels = [];
|
|
29
|
+
const data = [];
|
|
30
|
+
const type = result.meta.result.chart_type;
|
|
31
|
+
const title = result.meta.result.title;
|
|
32
|
+
const columns = result.meta.result.columns;
|
|
33
|
+
console.log(JSON.stringify(result));
|
|
34
|
+
result.meta.result.results.forEach(item => {
|
|
35
|
+
labels.push(item[columns[0]]);
|
|
36
|
+
data.push(parseFloat(item[columns[1]]));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Create the bar chart
|
|
40
|
+
const ctx = document.getElementById('myChart').getContext('2d');
|
|
41
|
+
const myChart = new Chart(ctx, {
|
|
42
|
+
type: type,
|
|
43
|
+
data: {
|
|
44
|
+
labels: labels,
|
|
45
|
+
datasets: [{
|
|
46
|
+
label: title,
|
|
47
|
+
data: data,
|
|
48
|
+
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
|
49
|
+
borderColor: 'rgba(75, 192, 192, 1)',
|
|
50
|
+
borderWidth: 1
|
|
51
|
+
}]
|
|
52
|
+
},
|
|
53
|
+
options: {
|
|
54
|
+
responsive: true,
|
|
55
|
+
plugins: {
|
|
56
|
+
legend: {
|
|
57
|
+
position: 'top',
|
|
58
|
+
},
|
|
59
|
+
title: {
|
|
60
|
+
display: true,
|
|
61
|
+
text: title
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
scales: {
|
|
65
|
+
y: {
|
|
66
|
+
beginAtZero: true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
})
|
|
72
|
+
.catch(error => console.error('Error fetching data:', error));
|
|
73
|
+
|
|
74
|
+
</script>
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT strftime('%Y-%m', date(`Order`.OrderDate)) AS OrderMonth, COUNT(`Order`.Id) AS OrderCount FROM `Order` WHERE `Order`.OrderDate IS NOT NULL GROUP BY OrderMonth ORDER BY OrderMonth;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from database import models
|
|
2
|
+
import logging
|
|
3
|
+
from safrs import jsonapi_attr
|
|
4
|
+
from sqlalchemy.orm import relationship, remote, foreign
|
|
5
|
+
from functools import wraps # This convenience func preserves name and docstring
|
|
6
|
+
import decimal as decimal
|
|
7
|
+
from sqlalchemy import extract, func
|
|
8
|
+
from flask import request, jsonify
|
|
9
|
+
from safrs import jsonapi_rpc, SAFRSAPI
|
|
10
|
+
from database import models
|
|
11
|
+
import safrs
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
Called by iFrame in home.js when admin app is loaded
|
|
16
|
+
|
|
17
|
+
Returns iFrame for ui/admin/home.js, with entries like this to populate the dashboard:
|
|
18
|
+
<iframe src="http://localhost:5656/chart_graphics/sales_per_category" style="flex: 1; border: none; width: 90%; height: 200px;">
|
|
19
|
+
|
|
20
|
+
curl -X GET "http://localhost:5656/dashboard"
|
|
21
|
+
|
|
22
|
+
You typically do not alter this file - it is rebuilt on `als genai-graphics`
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
app_logger = logging.getLogger(__name__)
|
|
27
|
+
dashboard_result = {}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@app.route('/dashboard', methods=['GET','OPTIONS'])
|
|
34
|
+
def dashboard():
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if request.method == 'OPTIONS':
|
|
38
|
+
return jsonify({"result": "ok"})
|
|
39
|
+
|
|
40
|
+
server = request.host_url
|
|
41
|
+
|
|
42
|
+
iframe_template = '<div class="dashboard-iframe"><iframe src="{url}" style="flex: 1; border: none; width: 90%; height: 200px;"></iframe></div>'
|
|
43
|
+
|
|
44
|
+
iframe_1 = iframe_template.format(url=f"{server}chart_graphics/sales_by_category")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
iframe_2 = iframe_template.format(url=f"{server}chart_graphics/number_of_sales_per_category")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
iframe_3 = iframe_template.format(url=f"{server}chart_graphics/count_orders_by_month")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
return f'<div style="display: flex; flex-direction: row; gap: 10px; border: none; ">{iframe_1} {iframe_2} {iframe_3}</div>'
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.route('/chart_graphics/<path:path>', methods=['GET','OPTIONS'])
|
|
60
|
+
def chart_graphics(path):
|
|
61
|
+
if request.method == 'OPTIONS':
|
|
62
|
+
return jsonify({"result": "ok"})
|
|
63
|
+
|
|
64
|
+
dashboards = get_dashboards()
|
|
65
|
+
if len(dashboards) == 0:
|
|
66
|
+
return jsonify({"result": "No dashboards Found"}), 404
|
|
67
|
+
if path in dashboards:
|
|
68
|
+
return dashboards[path]
|
|
69
|
+
return jsonify({"result": "not found"}), 404
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
##############################
|
|
73
|
+
# generated queries follow
|
|
74
|
+
##############################
|
|
75
|
+
|
|
76
|
+
def get_dashboards():
|
|
77
|
+
if len(dashboard_result) > 0:
|
|
78
|
+
return dashboard_result
|
|
79
|
+
|
|
80
|
+
from jinja2 import Environment, FileSystemLoader
|
|
81
|
+
env = Environment(loader = FileSystemLoader('ui/templates'))
|
|
82
|
+
template = env.get_template('bar_chart.jinja')
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
previously_failed = Path('docs/graphics/sales_by_category.err').exists()
|
|
87
|
+
if previously_failed:
|
|
88
|
+
pass # query has previously failed, so skip it
|
|
89
|
+
else:
|
|
90
|
+
try:
|
|
91
|
+
results = models.Category.sales_by_category(None)
|
|
92
|
+
color = 'rgba(75, 192, 192, 0.2)'
|
|
93
|
+
dashboard1 = template.render(result=results, color=color)
|
|
94
|
+
dashboard_result['sales_by_category']= dashboard1
|
|
95
|
+
except Exception as e:
|
|
96
|
+
msg = f"GenAI query creation error on models.Category.sales_by_category: " + str(e)
|
|
97
|
+
dashboard_result['sales_by_category'] = msg
|
|
98
|
+
app_logger.error(msg)
|
|
99
|
+
with open('docs/graphics//sales_by_category.err', 'w') as err_file:
|
|
100
|
+
err_file.write(msg) # this logs the error to prevent future calls
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
previously_failed = Path('docs/graphics/number_of_sales_per_category.err').exists()
|
|
106
|
+
if previously_failed:
|
|
107
|
+
pass # query has previously failed, so skip it
|
|
108
|
+
else:
|
|
109
|
+
try:
|
|
110
|
+
results = models.Category.number_of_sales_per_category(None)
|
|
111
|
+
color = 'rgba(75, 192, 192, 0.2)'
|
|
112
|
+
dashboard2 = template.render(result=results, color=color)
|
|
113
|
+
dashboard_result['number_of_sales_per_category']= dashboard2
|
|
114
|
+
except Exception as e:
|
|
115
|
+
msg = f"GenAI query creation error on models.Category.number_of_sales_per_category: " + str(e)
|
|
116
|
+
dashboard_result['number_of_sales_per_category'] = msg
|
|
117
|
+
app_logger.error(msg)
|
|
118
|
+
with open('docs/graphics//number_of_sales_per_category.err', 'w') as err_file:
|
|
119
|
+
err_file.write(msg) # this logs the error to prevent future calls
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
previously_failed = Path('docs/graphics/count_orders_by_month.err').exists()
|
|
125
|
+
if previously_failed:
|
|
126
|
+
pass # query has previously failed, so skip it
|
|
127
|
+
else:
|
|
128
|
+
try:
|
|
129
|
+
results = models.Order.count_orders_by_month(None)
|
|
130
|
+
color = 'rgba(75, 192, 192, 0.2)'
|
|
131
|
+
dashboard3 = template.render(result=results, color=color)
|
|
132
|
+
dashboard_result['count_orders_by_month']= dashboard3
|
|
133
|
+
except Exception as e:
|
|
134
|
+
msg = f"GenAI query creation error on models.Order.count_orders_by_month: " + str(e)
|
|
135
|
+
dashboard_result['count_orders_by_month'] = msg
|
|
136
|
+
app_logger.error(msg)
|
|
137
|
+
with open('docs/graphics//count_orders_by_month.err', 'w') as err_file:
|
|
138
|
+
err_file.write(msg) # this logs the error to prevent future calls
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
return dashboard_result
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from flask import request, jsonify
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
from config.config import Args # circular import error if at top
|
|
9
|
+
|
|
10
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
11
|
+
|
|
12
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
def get_server_url():
|
|
16
|
+
""" return the server URL for the OpenAPI spec """
|
|
17
|
+
result = f'http://{Args.instance.swagger_host}:{Args.instance.swagger_port}'
|
|
18
|
+
# get env variable API_LOGIC_SERVER_TUNNEL (or None)
|
|
19
|
+
if tunnel_url := os.getenv("API_LOGIC_SERVER_TUNNEL", None):
|
|
20
|
+
app_logger.info(f".. tunnel URL: {tunnel_url}")
|
|
21
|
+
result = tunnel_url
|
|
22
|
+
return result # + '/api'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@app.before_request
|
|
26
|
+
def before_any_request():
|
|
27
|
+
# print(f"[DEBUG] Incoming request: {request.method} {request.url}")
|
|
28
|
+
if activate_openapi_logging := False:
|
|
29
|
+
if request.content_type == 'application/json' and request.method in ['POST', 'PUT', 'PATCH']:
|
|
30
|
+
# openapi: Incoming request: PATCH http://localhost:5656/api/Customer/1/ {'data': {'attributes': {'credit_limit': 5555}, 'type': 'Customer', 'id': '1'}}
|
|
31
|
+
# openapi: Incoming request: PATCH http://6f6f-2601-644-4900-d6f0-ecc9-6df3-8863-c5b2.ngrok-free.app/api/Customer/1 {'credit_limit': 5555}
|
|
32
|
+
|
|
33
|
+
app_logger.info(f"openapi: Incoming request: {request.method} {request.url} {str(request.json)}")
|
|
34
|
+
else:
|
|
35
|
+
app_logger.info(f"openapi: Incoming request: {request.method} {request.url}")
|
|
36
|
+
# app_logger.info(f"openapi: Incoming request headers: {request.headers}")
|
|
37
|
+
|
|
38
|
+
chatgpt_request_json = {
|
|
39
|
+
"credit_limit": 25000,
|
|
40
|
+
}
|
|
41
|
+
standard_request_json = {
|
|
42
|
+
"data": {
|
|
43
|
+
"type": "Customer",
|
|
44
|
+
"id": "ALFKI",
|
|
45
|
+
"attributes": {
|
|
46
|
+
"name": "Alice",
|
|
47
|
+
"credit_limit": 25000,
|
|
48
|
+
"balance": 12345
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
swagger_request_json = {
|
|
53
|
+
'data': {
|
|
54
|
+
'attributes': {
|
|
55
|
+
'credit_limit': 5555
|
|
56
|
+
},
|
|
57
|
+
'type': 'Customer',
|
|
58
|
+
'id': '1'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@app.route('/.well-known/mcp.json', methods=['GET'])
|
|
65
|
+
def mcp_discovery(path=None):
|
|
66
|
+
''' called by mcp_client_executor for discovery; read docs/mcp_learning/ for:
|
|
67
|
+
1. learning, including fan-out & response format, and email requests.
|
|
68
|
+
2. schema
|
|
69
|
+
|
|
70
|
+
see: https://apilogicserver.github.io/Docs/Integration-MCP/#1-discovery
|
|
71
|
+
|
|
72
|
+
test: curl -X GET "http://localhost:5656/.well-known/mcp.json"
|
|
73
|
+
'''
|
|
74
|
+
# return docs/mcp_schema.json
|
|
75
|
+
mcp_learning_path = Path(project_dir + "/docs/mcp_learning")
|
|
76
|
+
try:
|
|
77
|
+
schema_path = mcp_learning_path.joinpath('mcp_schema.json')
|
|
78
|
+
if not schema_path.exists():
|
|
79
|
+
return jsonify({"error": "System Error - /docs/mcp_learning/mcp_schema.json not found"}), 404
|
|
80
|
+
with open(schema_path, "r") as schema_file:
|
|
81
|
+
schema = json.load(schema_file)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
app_logger.error(f"Error loading MCP schema: {e}")
|
|
84
|
+
return jsonify({"error": "MCP schema not found"}), 404
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
learnings_path = mcp_learning_path.joinpath('mcp.prompt')
|
|
88
|
+
if not learnings_path.exists():
|
|
89
|
+
return jsonify({"error": "System Error - /docs/mcp_learning/mcp,prompt not found"}), 404
|
|
90
|
+
with open(learnings_path, "r") as learnings_file:
|
|
91
|
+
learnings = learnings_file.read()
|
|
92
|
+
except Exception as e:
|
|
93
|
+
app_logger.error(f"Error loading MCP learnings: {e}")
|
|
94
|
+
return jsonify({"error": "MCP learnings not found"}), 404
|
|
95
|
+
schema['learning'] = learnings
|
|
96
|
+
|
|
97
|
+
return jsonify(schema), 200
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from flask import request, jsonify
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
5
|
+
|
|
6
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators = []):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
@app.route('/hello_service')
|
|
10
|
+
def hello_service():
|
|
11
|
+
"""
|
|
12
|
+
Illustrates:
|
|
13
|
+
* Use standard Flask, here for non-database endpoints.
|
|
14
|
+
|
|
15
|
+
Test it with:
|
|
16
|
+
|
|
17
|
+
http://localhost:5656/hello_service?user=ApiLogicServer
|
|
18
|
+
"""
|
|
19
|
+
user = request.args.get('user')
|
|
20
|
+
app_logger.info(f'{user}')
|
|
21
|
+
return jsonify({"result": f'hello from new_service! from {user}'})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from flask import request, jsonify
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
app_logger = logging.getLogger("api_logic_server_app")
|
|
5
|
+
|
|
6
|
+
def add_service(app, api, project_dir, swagger_host: str, PORT: str, method_decorators ):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
@app.route('/hello_newer_service')
|
|
10
|
+
def hello_newer_service():
|
|
11
|
+
"""
|
|
12
|
+
Illustrates:
|
|
13
|
+
* Use standard Flask, here for non-database endpoints.
|
|
14
|
+
|
|
15
|
+
Test it with:
|
|
16
|
+
|
|
17
|
+
http://localhost:5656/hello_newer_service?user=ApiLogicServer
|
|
18
|
+
"""
|
|
19
|
+
user = request.args.get('user')
|
|
20
|
+
app_logger.info(f'{user}')
|
|
21
|
+
return jsonify({"result": f'hello from even_newer_service! from {user}'})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Bar Chart Example</title>
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<canvas id="myChart" width="400" height="200"></canvas>
|
|
11
|
+
<script>
|
|
12
|
+
// Input data
|
|
13
|
+
fetch('http://localhost:5656/api/GraphicsServices/number_of_sales_per_category', {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: {
|
|
16
|
+
'accept': 'application/vnd.api+json',
|
|
17
|
+
'Content-Type': 'application/json'
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify({}) // Add any required payload here
|
|
20
|
+
})
|
|
21
|
+
.then(response => {
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
24
|
+
}
|
|
25
|
+
return response.json();
|
|
26
|
+
})
|
|
27
|
+
.then(result => {
|
|
28
|
+
const labels = [];
|
|
29
|
+
const data = [];
|
|
30
|
+
const type = result.meta.result.chart_type;
|
|
31
|
+
const title = result.meta.result.title;
|
|
32
|
+
const columns = result.meta.result.columns;
|
|
33
|
+
console.log(JSON.stringify(result));
|
|
34
|
+
result.meta.result.results.forEach(item => {
|
|
35
|
+
labels.push(item[columns[0]]);
|
|
36
|
+
data.push(parseFloat(item[columns[1]]));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Create the bar chart
|
|
40
|
+
const ctx = document.getElementById('myChart').getContext('2d');
|
|
41
|
+
const myChart = new Chart(ctx, {
|
|
42
|
+
type: type,
|
|
43
|
+
data: {
|
|
44
|
+
labels: labels,
|
|
45
|
+
datasets: [{
|
|
46
|
+
label: title,
|
|
47
|
+
data: data,
|
|
48
|
+
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
|
49
|
+
borderColor: 'rgba(75, 192, 192, 1)',
|
|
50
|
+
borderWidth: 1
|
|
51
|
+
}]
|
|
52
|
+
},
|
|
53
|
+
options: {
|
|
54
|
+
responsive: true,
|
|
55
|
+
plugins: {
|
|
56
|
+
legend: {
|
|
57
|
+
position: 'top',
|
|
58
|
+
},
|
|
59
|
+
title: {
|
|
60
|
+
display: true,
|
|
61
|
+
text: title
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
scales: {
|
|
65
|
+
y: {
|
|
66
|
+
beginAtZero: true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
})
|
|
72
|
+
.catch(error => console.error('Error fetching data:', error));
|
|
73
|
+
|
|
74
|
+
</script>
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SELECT Category.CategoryName, COUNT(`Order.Id`) AS NumberOfSales FROM Category JOIN Product ON Product.CategoryId = Category.Id JOIN OrderDetail ON OrderDetail.ProductId = Product.Id JOIN `Order` ON `Order`.Id = OrderDetail.OrderId WHERE `Order`.ShippedDate IS NOT NULL GROUP BY Category.CategoryName ORDER BY NumberOfSales DESC;
|