ApiLogicServer 15.0.52__py3-none-any.whl → 15.0.54__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.
Files changed (31) hide show
  1. api_logic_server_cli/api_logic_server.py +3 -1
  2. api_logic_server_cli/api_logic_server_info.yaml +3 -3
  3. api_logic_server_cli/create_from_model/api_logic_server_utils.py +6 -0
  4. api_logic_server_cli/prototypes/base/.github/.copilot-instructions.md +39 -3
  5. api_logic_server_cli/prototypes/base/database/alembic/alembic_run.py +98 -0
  6. api_logic_server_cli/prototypes/base/database/alembic/readme_alembic.md +36 -0
  7. api_logic_server_cli/prototypes/base/docs/training/admin_app_1_context.prompt.md +40 -0
  8. api_logic_server_cli/prototypes/base/integration/mcp/mcp_client_executor.py +2 -2
  9. api_logic_server_cli/prototypes/basic_demo/_config.yml +8 -0
  10. api_logic_server_cli/prototypes/basic_demo/_layouts/redirect.html +15 -0
  11. api_logic_server_cli/prototypes/basic_demo/docs/system-creation-vibe.md +161 -0
  12. api_logic_server_cli/prototypes/basic_demo/logic/declarative-vs-procedural-comparison.html +110 -0
  13. api_logic_server_cli/prototypes/basic_demo/logic/procedural/declarative-vs-procedural-comparison.md +295 -0
  14. api_logic_server_cli/prototypes/manager/samples/prompts/add_email.prompt +8 -0
  15. api_logic_server_cli/prototypes/manager/samples/prompts/elections.prompt +3 -0
  16. api_logic_server_cli/prototypes/manager/samples/prompts/emp_dept.prompt +4 -0
  17. api_logic_server_cli/prototypes/manager/samples/prompts/genai_demo.prompt +13 -0
  18. api_logic_server_cli/prototypes/manager/samples/readme_samples.md +3 -1
  19. api_logic_server_cli/prototypes/manager/system/genai/app_templates/app_learning/Admin-App-Resource-Learning-Prompt.md +1 -1
  20. api_logic_server_cli/prototypes/manager/system/genai/app_templates/react-admin-template/package.json +1 -0
  21. api_logic_server_cli/sqlacodegen_wrapper/sqlacodegen_wrapper.py +5 -2
  22. {apilogicserver-15.0.52.dist-info → apilogicserver-15.0.54.dist-info}/METADATA +1 -1
  23. {apilogicserver-15.0.52.dist-info → apilogicserver-15.0.54.dist-info}/RECORD +27 -20
  24. api_logic_server_cli/prototypes/base/.devcontainer-option/.copilot-instructions.md +0 -178
  25. api_logic_server_cli/prototypes/base/database/alembic/readme.md +0 -18
  26. api_logic_server_cli/prototypes/base/database/system/SAFRSBaseX.pyZ +0 -73
  27. api_logic_server_cli/prototypes/basic_demo/customizations/database/system/SAFRSBaseX.py +0 -139
  28. {apilogicserver-15.0.52.dist-info → apilogicserver-15.0.54.dist-info}/WHEEL +0 -0
  29. {apilogicserver-15.0.52.dist-info → apilogicserver-15.0.54.dist-info}/entry_points.txt +0 -0
  30. {apilogicserver-15.0.52.dist-info → apilogicserver-15.0.54.dist-info}/licenses/LICENSE +0 -0
  31. {apilogicserver-15.0.52.dist-info → apilogicserver-15.0.54.dist-info}/top_level.txt +0 -0
@@ -12,9 +12,10 @@ ApiLogicServer CLI: given a database url, create [and run] customizable ApiLogic
12
12
  Called from api_logic_server_cli.py, by instantiating the ProjectRun object.
13
13
  '''
14
14
 
15
- __version__ = "15.00.52" # last public release: 15.00.47 (15.00.12)
15
+ __version__ = "15.00.54" # last public release: 15.00.52 (15.00.12)
16
16
  recent_changes = \
17
17
  f'\n\nRecent Changes:\n' +\
18
+ "\t07/23/2024 - 15.00.54: system vibe support \n"\
18
19
  "\t07/20/2024 - 15.00.52: Python 3.13 compatibility fixes - psycopg2→psycopg3, SQLAlchemy 2.0+, pkg_resources→importlib.metadata. mgr dbs \n"\
19
20
  "\t07/17/2024 - 15.00.49: venv fix+, ext bldr * fix, copilot vibe tweaks - creation, mcp logic, basic_demo autonums \n"\
20
21
  "\t07/10/2024 - 15.00.41: copilot vibe support for logic, UI, MCP, bug[98] \n"\
@@ -390,6 +391,7 @@ def create_project_and_overlay_prototypes(project: 'ProjectRun', msg: str) -> st
390
391
  joinpath('prototypes/basic_demo')
391
392
  recursive_overwrite(nw_dir, project.project_directory)
392
393
  create_utils.copy_md(project = project, from_doc_file = "Sample-Basic-Demo.md")
394
+ create_utils.copy_md(project = project, from_doc_file = "Sample-Basic-Demo-Vibe.md", to_project_file="README-VIBE.md")
393
395
 
394
396
 
395
397
  if project.db_url == "mysql+pymysql://root:p@localhost:3306/classicmodels":
@@ -1,3 +1,3 @@
1
- last_created_date: July 20, 2025 08:02:02
2
- last_created_project_name: samples/nw_sample_nocust
3
- last_created_version: 15.00.52
1
+ last_created_date: July 23, 2025 12:12:14
2
+ last_created_project_name: ../../../servers/basic_demo
3
+ last_created_version: 15.00.53
@@ -197,6 +197,12 @@ def copy_md(project, from_doc_file: str, to_project_file: str = "README.md"):
197
197
  else:
198
198
  each_line = each_line.replace('.md', '') # hmm... todo: find out why this exists
199
199
  pass
200
+ if from_doc_file == 'Sample-Basic-Demo-Vibe.md' and 'title=' in each_line:
201
+ # extract the quoted text after 'title=' into title
202
+ match = re.search(r'title\s*=\s*["\']([^"\']+)["\']', each_line)
203
+ if match:
204
+ title = match.group(1)
205
+ readme_lines_md.append('**' + title + ':**\n')
200
206
  readme_lines_md.append(each_line)
201
207
  with open(str(to_file), "w") as readme_file:
202
208
  readme_file.writelines(readme_lines_md)
@@ -37,11 +37,14 @@ python api_logic_server_run.py
37
37
  Rule.sum(derive=Customer.Balance, as_sum_of=Order.AmountTotal)
38
38
  Rule.constraint(validate=Customer, as_condition=lambda row: row.Balance <= row.CreditLimit)
39
39
  ```
40
+
41
+ As described in `docs/training/logic_bank_api.prompt`, logic includes rules for sums, formulas, constraints, and more. They also include events, which are used to trigger actions like sending emails or updating related records.
42
+
40
43
  For project `basic_demo`, the best place to begin is to provide CoPilot with the natural language logic below. CoPilot will then offer to translate the logic into LogicBank rules. Also
41
44
  1. Provide the analysis (below) showing the 44X code reduction.
42
45
  2. Stress the importance of business logic - it's half the project, and requires an underlying rules engine. Using AI alone is not sufficient - you will wind up with procedural code, with the issues noted in the analysis below.
43
46
 
44
- Here's the natural language logic to provide to CoPilotP
47
+ Here's the natural language logic to provide to CoPilot:
45
48
 
46
49
  ```text
47
50
  Use case: Check Credit
@@ -126,6 +129,8 @@ Then, `npm install` and `npm start`
126
129
 
127
130
  Temporary restriction: security must be disabled.
128
131
 
132
+ **IMPORTANT**: When working with React apps, ALWAYS read `docs/training` first. This file contains critical data access provider configuration that was built when the project was created. The data provider handles JSON:API communication and record context - ignore this at your peril.
133
+
129
134
  Customize using CoPilot chat, with `docs/training`.
130
135
 
131
136
  ### Security - Role-Based Access Control
@@ -164,9 +169,13 @@ def my_endpoint():
164
169
  return {"message": "Custom endpoint"}
165
170
  ```
166
171
 
167
- ### Customize Models - Add Attributes
172
+ ### Customize Models - Add Tables, Attributes
173
+
174
+ Update `database/model.py`, and use `database/alembic` to update the database (highly impactful - request permission). This is *generally* preferrable to updating the database directly, since some platforms may not have database CLI tools.
168
175
 
169
- Update the model, and use `database/alembic` to update the database (highly impactful - request permission).
176
+ If altering `database/models.py`, be sure to follow the patterns shown in the existing models. Note they not typically contain a `__bind_key__`.
177
+
178
+ ```python
170
179
 
171
180
  ### Addressing `Missing Attributes` during logic loading at project startup
172
181
 
@@ -216,6 +225,33 @@ Convert to Python values first using float(), int(), str()
216
225
  Use property() function instead of @jsonapi_attr for computed properties
217
226
  Always add error handling for type conversions
218
227
 
228
+ ### Adding events
229
+ LogicBank rules are the preferred approach to logic, but you will sometimes need to add events. This is done in `logic/declare_logic.py` (important: the function MUST come first):
230
+
231
+ ```python
232
+ # Example: Log email activity after SysEmail is committed
233
+
234
+ def sys_email_after_commit(row: models.SysEmail, old_row: models.SysEmail, logic_row: LogicRow):
235
+ """
236
+ After SysEmail is committed, log 'email sent'
237
+ unless the customer has opted out
238
+ """
239
+ if not row.customer.email_opt_out:
240
+ logic_row.log(f"📧 Email sent to {row.customer.name} - Subject: {row.subject}")
241
+ else:
242
+ logic_row.log(f"🚫 Email blocked for {row.customer.name} - Customer opted out")
243
+
244
+ Rule.commit_row_event(on_class=SysEmail, calling=sys_email_after_commit)
245
+ ```
246
+
247
+ LogicBank event types include:
248
+ - `Rule.commit_row_event()` - fires after transaction commits
249
+ - `Rule.after_insert()` - fires after row insert
250
+ - `Rule.after_update()` - fires after row update
251
+ - `Rule.after_delete()` - fires after row delete
252
+
253
+ All events receive `(row, old_row, logic_row)` parameters and should use `logic_row.log()` for logging.
254
+
219
255
  ## 📁 Key Directories
220
256
 
221
257
  - `logic/` - Business rules (declarative)
@@ -0,0 +1,98 @@
1
+ import os
2
+ import subprocess
3
+ import sys
4
+
5
+ '''
6
+ Pushes the current database/models.py to the database.
7
+ python database/alembic/alembic_run.py [--non-interactive]
8
+ '''
9
+
10
+ def prompt(msg, non_interactive=False):
11
+ if non_interactive:
12
+ print(f"{msg}")
13
+ print("Running in non-interactive mode...")
14
+ return
15
+ try:
16
+ input(f"{msg}\nPress Enter to continue or Ctrl+C to abort...")
17
+ except EOFError:
18
+ print("Running in non-interactive mode (no stdin available)...")
19
+ return
20
+
21
+ def run(cmd, env=None):
22
+ print(f"Running: {cmd}")
23
+ result = subprocess.run(cmd, shell=True, env=env)
24
+ if result.returncode != 0:
25
+ print(f"Command failed: {cmd}")
26
+ sys.exit(result.returncode)
27
+
28
+ def main():
29
+ # Check for non-interactive mode more safely
30
+ non_interactive = "--non-interactive" in sys.argv
31
+ if not non_interactive:
32
+ try:
33
+ non_interactive = not sys.stdin.isatty()
34
+ except:
35
+ # If we can't check stdin, assume non-interactive
36
+ non_interactive = True
37
+
38
+ orig_dir = os.getcwd()
39
+ # Change to the database directory (parent of alembic directory) where alembic.ini is located
40
+ db_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
41
+ print(f"Changing directory to {db_dir}")
42
+ os.chdir(db_dir)
43
+
44
+ env = os.environ.copy()
45
+ env["APILOGICPROJECT_NO_FLASK"] = "True" # ~ export APILOGICPROJECT_NO_FLASK=True
46
+
47
+ print("\n\nThis script will update your database schema to match models.py using Alembic.")
48
+ print("Steps:")
49
+ print("1. Set APILOGICPROJECT_NO_FLASK=True -- eg, export APILOGICPROJECT_NO_FLASK=True")
50
+ print("2. Run: alembic upgrade head")
51
+ print("3. Run: alembic revision --autogenerate -m \"message\"")
52
+ prompt("Ready to proceed?", non_interactive)
53
+
54
+ run("alembic upgrade head", env=env)
55
+ print("Database schema updated to latest migration.")
56
+
57
+ prompt("Now, a new migration will be generated to match models.py.", non_interactive)
58
+ if non_interactive:
59
+ msg = "autogenerated"
60
+ else:
61
+ try:
62
+ msg = input("Enter a message for the migration (default: 'autogenerated'): ") or "autogenerated"
63
+ except EOFError:
64
+ msg = "autogenerated"
65
+ run(f'alembic revision --autogenerate -m "{msg}"', env=env)
66
+
67
+ # Find the latest migration file
68
+ versions_dir = os.path.join(db_dir, "alembic", "versions")
69
+ migration_files = sorted(
70
+ [f for f in os.listdir(versions_dir) if f.endswith(".py")],
71
+ key=lambda x: os.path.getmtime(os.path.join(versions_dir, x)),
72
+ reverse=True
73
+ )
74
+ if migration_files:
75
+ latest_file = os.path.join(versions_dir, migration_files[0])
76
+ with open(latest_file, "r") as f:
77
+ lines = f.readlines()
78
+ with open(latest_file, "w") as f:
79
+ for line in lines:
80
+ f.write(line)
81
+ if line.strip().startswith("def downgrade"):
82
+ f.write(" return\n")
83
+ break
84
+ else:
85
+ print("No migration file found.")
86
+ sys.exit(1)
87
+
88
+ # Apply the newly generated migration after modifying the migration file
89
+ prompt(f"Migration file generated: {latest_file}\nIt is recommended to review this migration file before proceeding.", non_interactive)
90
+ run("alembic upgrade head", env=env)
91
+
92
+ print("Migration file updated: downgrade will do nothing.")
93
+ print("Consider updating ui/admin/admin.yaml to reflect schema changes.")
94
+ os.chdir(orig_dir)
95
+ print("\nSuccess! Database schema and migrations are up to date.\n\n")
96
+
97
+ if __name__ == "__main__":
98
+ main()
@@ -0,0 +1,36 @@
1
+ You can push changes to `database/models.py' to your database automatically, or manually.
2
+
3
+ <br>
4
+
5
+ ## Automatic
6
+
7
+ Use:
8
+
9
+ ```bash
10
+ python database/alembic/alembic_run.py [--non-interactive]
11
+ ```
12
+
13
+ <br>
14
+
15
+ ## Manual
16
+
17
+ The diagram below illustrates a path for enacting changes to the data model, and using [Alembic](https://alembic.sqlalchemy.org/en/latest/index.html) to automate the database changes:
18
+
19
+ 1. Update `database/models.py` (e.g., add columns, tables)
20
+ 2. Use alembic to compute the revisions
21
+ ```bash
22
+ cd database
23
+ export APILOGICPROJECT_NO_FLASK=True
24
+ alembic revision --autogenerate -m "Added Tables and Columns"
25
+ ```
26
+ 3. **Edit the revision file** to signify your understanding (see below)
27
+ 4. Activate the change
28
+ ```bash
29
+ alembic upgrade head
30
+ unset APILOGICPROJECT_NO_FLASK
31
+ ```
32
+
33
+ ![alembic example](https://github.com/ApiLogicServer/Docs/blob/main/docs/images/database/alembic/alembic-overview.png?raw=true)
34
+
35
+
36
+ To update your admin app, run `rebuild-from-model`. For more information, see [Database Design Changes](https://apilogicserver.github.io/Docs/Database-Changes/).
@@ -1,3 +1,43 @@
1
1
 
2
2
  Generate a full React Admin application using the following instructions.
3
3
  The result must be a runnable React app (`npm start`) that connects to the supplied JSON:API, with fully implemented components (no placeholders or empty files).
4
+
5
+ ## Critical Data Access Provider Configuration
6
+
7
+ This project uses a **pre-configured JSON:API data provider** that was built when the project was created.
8
+
9
+ ### Key Requirements:
10
+
11
+ 1. **Data Provider**: Use the existing `jsonapiClient` from `./rav4-jsonapi-client/ra-jsonapi-client`
12
+ 2. **Record Context**: For custom components (like cards), ALWAYS wrap with `<RecordContextProvider value={record}>`
13
+ 3. **List Data Access**: Use `useListContext()` to get data and loading state
14
+ 4. **Individual Records**: Use `useRecordContext()` to access record data within providers
15
+ 5. **API Root**: The data provider connects to `conf.api_root` (typically `http://localhost:5656/api`)
16
+
17
+ ### Example Pattern for Custom List Views:
18
+ ```javascript
19
+ import { useListContext, RecordContextProvider, useRecordContext } from 'react-admin';
20
+
21
+ const CustomGrid = () => {
22
+ const { data, isLoading } = useListContext();
23
+
24
+ return (
25
+ <Grid container>
26
+ {data?.map(record => (
27
+ <Grid item key={record.id}>
28
+ <RecordContextProvider value={record}>
29
+ <CustomCard />
30
+ </RecordContextProvider>
31
+ </Grid>
32
+ ))}
33
+ </Grid>
34
+ );
35
+ };
36
+
37
+ const CustomCard = () => {
38
+ const record = useRecordContext();
39
+ return <Card>{record.name}</Card>;
40
+ };
41
+ ```
42
+
43
+ ### CRITICAL: Do NOT create new data providers or modify the existing JSON:API client configuration. The project's data flow depends on the pre-built provider.
@@ -147,12 +147,12 @@ def query_llm_with_nl(learnings_and_schema: str, nl_query: str):
147
147
  else:
148
148
  # read integration/mcp/mcp_tool_context.json
149
149
  tool_context_file_path = os.path.join(os.path.dirname(__file__), "../../integration/mcp/examples/mcp_tool_context_response_get.json")
150
- if nl_query == default_query_email:
150
+ if 'send email' in nl_query:
151
151
  tool_context_file_path = os.path.join(os.path.dirname(__file__), "../../integration/mcp/examples/mcp_tool_context_response.json")
152
152
  try:
153
153
  with open(tool_context_file_path, "r") as tool_context_file:
154
154
  tool_context_str = tool_context_file.read()
155
- # log.info(f"\n\n2c. Tool context from file {tool_context_file_path}:\n" + tool_context_str)
155
+ log.info(f"\n\n2c. Tool context from file {tool_context_file_path}:\n" + tool_context_str)
156
156
  except FileNotFoundError:
157
157
  raise ConstraintException(f"Tool context file not found at {tool_context_file_path}.")
158
158
 
@@ -0,0 +1,8 @@
1
+ # GitHub Pages / Jekyll Configuration
2
+ # Enable processing of markdown files
3
+ markdown: kramdown
4
+ highlighter: rouge
5
+
6
+ # Include the _layouts directory
7
+ include:
8
+ - _layouts
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta http-equiv="refresh" content="1;url={{ page.redirect }}" />
6
+ <link rel="canonical" href="{{ page.redirect }}" />
7
+ <script type="text/javascript">
8
+ window.location.href = "{{ page.redirect }}"
9
+ </script>
10
+ <title>Page Redirection</title>
11
+ </head>
12
+ <body>
13
+ If you are not redirected automatically, follow <a href='{{ page.redirect }}'>this link</a>.
14
+ </body>
15
+ </html>
@@ -0,0 +1,161 @@
1
+ ---
2
+ title: Vibe MCP / Microservice
3
+ version: 0.23 from docsite 7/11/2025
4
+ ---
5
+ <style>
6
+ .md-typeset h1,
7
+ .md-content__button {
8
+ display: none;
9
+ }
10
+ </style>
11
+
12
+ << Under Construction >>
13
+
14
+ # Vibe an MCP Microservice
15
+
16
+ See the Readme for detailed walk through.
17
+
18
+ Scenario: we have an existing database (customers, order, items and product), and we require:
19
+
20
+ * An MCP-enabled API, for integration
21
+ * MCP client support to send emails for overdue orders
22
+ * Business Logic to check credit by rolling up Items and Orders.
23
+
24
+ This is a record of how we created the system using CoPilot Chat (*Vibe*).
25
+
26
+ <br>
27
+
28
+ ## Initialize CoPilot
29
+
30
+ ```
31
+ Please find and read `.github/.copilot-instructions.md`.
32
+ ```
33
+
34
+ <br>
35
+
36
+ ## Create the System
37
+
38
+ ```bash title="Create a project from an existing database"
39
+ # in the GenAI-Logic Manager:
40
+ Create a database project from samples/dbs/basic_demo.sqlite
41
+ ```
42
+
43
+ <br>
44
+
45
+ ## Verify the API and Admin App
46
+
47
+ The project should automatically open a new window in VSCode. Again, open CoPilot and bootstrap it with: <br>
48
+
49
+ ```bash title="Initialize CoPilot"
50
+ Please find and read `.github/.copilot-instructions.md`**.
51
+ ```
52
+
53
+ Verify it as follows:
54
+
55
+ * **Click F5** to start server, open app at http://localhost:5656/
56
+ * **Verify API:** filtering, sorting, pagination, optimistic locking and related data access - see the Swagger
57
+ * **Verify Admin App:** multi-page, multi-table, automatic joins, lookups, cascade add - collaboration-ready
58
+
59
+ <br>
60
+
61
+ ## Create a Custom React App
62
+
63
+ **Create Start App**
64
+ ```bash title="Create a custom react app"
65
+ Create a react app.
66
+ ```
67
+
68
+ <br>
69
+
70
+ **Customize - add cards**
71
+ ```txt title='Customize using Natural Language'
72
+ In the ui/react app, update the Product list to provide users an option to see results in a list,
73
+ or in cards.
74
+ ```
75
+ <br>
76
+
77
+ todo - diagram
78
+
79
+ **Test: verify option for cards on `http://localhost:3000`**
80
+
81
+ <br>
82
+
83
+ ## MCP Client - Overdue Orders
84
+
85
+ **Stop the Server**
86
+
87
+ <br>
88
+
89
+ **Create MCP Executor: process request - invokes the LLM to obtain a series of API calls to run, and runs them**
90
+ ``` bash title="Create an MCP Client Executor"
91
+ Create the mcp client executor
92
+ ```
93
+ <br>
94
+
95
+ **Add a Table to Track Sent Emails**
96
+ ``` bash title="Add a Table to Track Sent Emails"
97
+ Create a table SysEmail in `database/db.sqlite` as a child of customer,
98
+ with columns id, message, subject, customer_id and CreatedOn.
99
+ ```
100
+ Follow the suggestions to update the admin app.
101
+
102
+ <br>
103
+
104
+ **Create the email service stub using SysEmail as a Request Table**
105
+ ``` bash title="Create the email service using SysEmail as a Request Table"
106
+ Add an after_flush event on SysEmail to produce a log message "email sent",
107
+ unless the customer has opted out.
108
+ ```
109
+
110
+ <br>
111
+
112
+ **Business Logic to Honor Email Opt-out**
113
+ ```bash title="Business Logic to Honor Email Opt-out"
114
+ Add an after_flush event on SysEmail to produce a log message "email sent",
115
+ unless the customer has opted out.
116
+ ```
117
+
118
+ <br>
119
+
120
+ **Test: restart the server/admin app - Click SysMCP >> Create New, and enter:**
121
+ ```text title="Test the MCP Client: Click SysMCP >> Create New, and enter"
122
+ List the orders date_shipped is null and CreatedOn before 2023-07-14,
123
+ and send a discount email (subject: 'Discount Offer') to the customer for each one.
124
+ ```
125
+
126
+ <br>
127
+ todo - diagram
128
+
129
+ **Use the Admin App to Verify the first customer has an email**
130
+
131
+ <br>
132
+
133
+ ## Check Credit Business Logic
134
+
135
+ **1. Stop the Server** (Red Stop button, or Shift-F5 -- see Appendix)
136
+
137
+ **2. Add Business Logic**
138
+
139
+ ```bash title="Check Credit Logic (instead of 220 lines of code)"
140
+ Use case: Check Credit
141
+ 1. The Customer's balance is less than the credit limit
142
+ 2. The Customer's balance is the sum of the Order amount_total where date_shipped is null
143
+ 3. The Order's amount_total is the sum of the Item amount
144
+ 4. The Item amount is the quantity * unit_price
145
+ 5. The Item unit_price is copied from the Product unit_price
146
+
147
+ Use case: App Integration
148
+ 1. Send the Order to Kafka topic 'order_shipping' if the date_shipped is not None.
149
+ ```
150
+
151
+ To test the logic:
152
+
153
+ **1. Use the Admin App to access the first order for `Customer Alice`**
154
+
155
+ **2. Edit its first item to a very high quantity**
156
+
157
+ todo - diagram
158
+
159
+ The update is properly rejected because it exceeds the credit limit. Observe the rules firing in the console log - see Logic In Action, below.
160
+
161
+ <br>
@@ -0,0 +1,110 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Page Moved - Declarative vs Procedural Comparison</title>
6
+ <meta http-equiv="refresh" content="3; url=https://apilogicserver.github.io/basic_demo/logic/procedural/declarative-vs-procedural-comparison.html">
7
+ <link rel="canonical" href="https://apilogicserver.github.io/basic_demo/logic/procedural/declarative-vs-procedural-comparison.html">
8
+ <style>
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
11
+ max-width: 600px;
12
+ margin: 50px auto;
13
+ padding: 20px;
14
+ line-height: 1.6;
15
+ }
16
+ .redirect-box {
17
+ border: 2px solid #007acc;
18
+ padding: 30px;
19
+ border-radius: 8px;
20
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
21
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
22
+ }
23
+ .countdown {
24
+ font-weight: bold;
25
+ color: #007acc;
26
+ font-size: 1.2em;
27
+ }
28
+ .manual-link {
29
+ display: inline-block;
30
+ background: #007acc;
31
+ color: white;
32
+ padding: 12px 24px;
33
+ text-decoration: none;
34
+ border-radius: 5px;
35
+ margin: 10px 0;
36
+ font-weight: bold;
37
+ }
38
+ .manual-link:hover {
39
+ background: #005c99;
40
+ }
41
+ h1 { color: #333; margin-top: 0; }
42
+ .progress-bar {
43
+ width: 100%;
44
+ height: 6px;
45
+ background: #ddd;
46
+ border-radius: 3px;
47
+ margin: 15px 0;
48
+ overflow: hidden;
49
+ }
50
+ .progress-fill {
51
+ height: 100%;
52
+ background: #007acc;
53
+ width: 100%;
54
+ transition: width 1s linear;
55
+ }
56
+ </style>
57
+ <script>
58
+ let seconds = 3;
59
+ function redirect() {
60
+ window.location.href = "https://apilogicserver.github.io/basic_demo/logic/procedural/declarative-vs-procedural-comparison.html";
61
+ }
62
+ function updateCountdown() {
63
+ const countdownElement = document.getElementById('countdown');
64
+ const progressElement = document.getElementById('progress-fill');
65
+
66
+ if (countdownElement) {
67
+ countdownElement.textContent = seconds;
68
+ }
69
+ if (progressElement) {
70
+ progressElement.style.width = ((3-seconds)/3 * 100) + '%';
71
+ }
72
+
73
+ if (seconds <= 0) {
74
+ redirect();
75
+ } else {
76
+ seconds--;
77
+ setTimeout(updateCountdown, 1000);
78
+ }
79
+ }
80
+ window.onload = function() {
81
+ updateCountdown();
82
+ // Fallback redirect in case JavaScript fails
83
+ setTimeout(redirect, 3100);
84
+ }
85
+ </script>
86
+ </head>
87
+ <body>
88
+ <div class="redirect-box">
89
+ <h1>📄 Page Moved</h1>
90
+ <p>The <strong>Declarative vs Procedural Logic Comparison</strong> has moved to a new location.</p>
91
+
92
+ <div class="progress-bar">
93
+ <div id="progress-fill" class="progress-fill" style="width: 0%;"></div>
94
+ </div>
95
+
96
+ <p>Redirecting automatically in <span id="countdown" class="countdown">3</span> seconds...</p>
97
+
98
+ <p><strong>If you are not redirected automatically:</strong></p>
99
+ <a href="https://apilogicserver.github.io/basic_demo/logic/procedural/declarative-vs-procedural-comparison.html" class="manual-link">
100
+ 👉 Go to New Location
101
+ </a>
102
+
103
+ <hr style="margin: 25px 0; border: none; border-top: 1px solid #ddd;">
104
+ <p><small>
105
+ <strong>New URL:</strong><br>
106
+ <code>https://apilogicserver.github.io/basic_demo/logic/procedural/declarative-vs-procedural-comparison.html</code>
107
+ </small></p>
108
+ </div>
109
+ </body>
110
+ </html>