odac 0.9.0 → 1.0.0
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.
- package/.github/workflows/auto-pr-description.yml +0 -2
- package/.github/workflows/codeql.yml +46 -0
- package/.github/workflows/release.yml +13 -6
- package/.github/workflows/test-coverage.yml +10 -9
- package/.releaserc.js +9 -6
- package/CHANGELOG.md +62 -150
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTING.md +8 -8
- package/LICENSE +21 -661
- package/README.md +12 -12
- package/SECURITY.md +4 -4
- package/bin/odac.js +101 -0
- package/{framework/web/candy.js → client/odac.js} +310 -44
- package/docs/backend/01-overview/{01-whats-in-the-candy-box.md → 01-whats-in-the-odac-box.md} +4 -2
- package/docs/backend/01-overview/02-super-handy-helper-functions.md +29 -1
- package/docs/backend/01-overview/03-development-server.md +11 -11
- package/docs/backend/02-structure/01-typical-project-layout.md +4 -4
- package/docs/backend/03-config/00-configuration-overview.md +6 -6
- package/docs/backend/03-config/01-database-connection.md +1 -1
- package/docs/backend/03-config/02-static-route-mapping-optional.md +4 -4
- package/docs/backend/03-config/04-environment-variables.md +20 -20
- package/docs/backend/03-config/05-early-hints.md +4 -4
- package/docs/backend/04-routing/01-basic-page-routes.md +4 -4
- package/docs/backend/04-routing/02-controller-less-view-routes.md +5 -5
- package/docs/backend/04-routing/03-api-and-data-routes.md +3 -3
- package/docs/backend/04-routing/04-authentication-aware-routes.md +5 -5
- package/docs/backend/04-routing/05-advanced-routing.md +3 -3
- package/docs/backend/04-routing/06-error-pages.md +17 -17
- package/docs/backend/04-routing/07-cron-jobs.md +13 -13
- package/docs/backend/04-routing/08-middleware.md +214 -0
- package/docs/backend/04-routing/09-websocket-auth-middleware.md +292 -0
- package/docs/backend/04-routing/09-websocket-examples.md +381 -0
- package/docs/backend/04-routing/09-websocket-quick-reference.md +211 -0
- package/docs/backend/04-routing/09-websocket.md +298 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +3 -3
- package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +41 -0
- package/docs/backend/05-controllers/03-controller-classes.md +19 -19
- package/docs/backend/05-forms/01-custom-forms.md +114 -114
- package/docs/backend/05-forms/02-automatic-database-insert.md +82 -82
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +26 -26
- package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +10 -10
- package/docs/backend/07-views/01-the-view-directory.md +1 -1
- package/docs/backend/07-views/02-rendering-a-view.md +22 -22
- package/docs/backend/07-views/03-template-syntax.md +52 -52
- package/docs/backend/07-views/03-variables.md +84 -84
- package/docs/backend/07-views/04-request-data.md +57 -57
- package/docs/backend/07-views/05-conditionals.md +78 -78
- package/docs/backend/07-views/06-loops.md +114 -114
- package/docs/backend/07-views/07-translations.md +66 -66
- package/docs/backend/07-views/08-backend-javascript.md +103 -103
- package/docs/backend/07-views/09-comments.md +71 -71
- package/docs/backend/08-database/01-database-connection.md +8 -8
- package/docs/backend/08-database/02-using-mysql.md +49 -49
- package/docs/backend/09-validation/01-the-validator-service.md +38 -38
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +15 -15
- package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +10 -10
- package/docs/backend/10-authentication/03-register.md +12 -12
- package/docs/backend/10-authentication/{04-candy-register-forms.md → 04-odac-register-forms.md} +141 -141
- package/docs/backend/10-authentication/05-session-management.md +10 -10
- package/docs/backend/10-authentication/{06-candy-login-forms.md → 06-odac-login-forms.md} +125 -125
- package/docs/backend/11-mail/01-the-mail-service.md +5 -5
- package/docs/backend/12-streaming/01-streaming-overview.md +96 -54
- package/docs/backend/13-utilities/{01-candy-var.md → 01-odac-var.md} +109 -109
- package/docs/frontend/01-overview/01-introduction.md +30 -30
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +45 -45
- package/docs/frontend/02-ajax-navigation/02-configuration.md +14 -14
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +36 -36
- package/docs/frontend/03-forms/01-form-handling.md +32 -32
- package/docs/frontend/04-api-requests/01-get-post.md +33 -33
- package/docs/frontend/05-streaming/01-client-streaming.md +15 -15
- package/docs/frontend/06-websocket/00-overview.md +76 -0
- package/docs/frontend/06-websocket/01-websocket-client.md +139 -0
- package/docs/frontend/06-websocket/02-shared-websocket.md +149 -0
- package/docs/index.json +49 -11
- package/eslint.config.mjs +6 -6
- package/{framework/index.js → index.js} +1 -1
- package/package.json +14 -39
- package/{framework/src → src}/Auth.js +59 -59
- package/{framework/src → src}/Config.js +3 -3
- package/{framework/src → src}/Lang.js +7 -7
- package/{framework/src → src}/Mail.js +5 -5
- package/{framework/src → src}/Mysql.js +42 -42
- package/src/Odac.js +112 -0
- package/{framework/src → src}/Request.js +38 -36
- package/{framework/src → src}/Route/Internal.js +116 -116
- package/src/Route/Middleware.js +75 -0
- package/src/Route.js +621 -0
- package/src/Server.js +22 -0
- package/{framework/src → src}/Stream.js +11 -3
- package/{framework/src → src}/Validator.js +21 -21
- package/{framework/src → src}/Var.js +5 -5
- package/{framework/src → src}/View/EarlyHints.js +1 -1
- package/{framework/src → src}/View/Form.js +69 -69
- package/{framework/src → src}/View.js +78 -81
- package/src/WebSocket.js +403 -0
- package/template/config.json +5 -0
- package/{web → template}/controller/page/about.js +6 -6
- package/{web → template}/controller/page/index.js +9 -9
- package/{web → template}/package.json +4 -5
- package/{web → template}/public/assets/css/style.css +4 -4
- package/{web → template}/public/assets/js/app.js +6 -6
- package/{web → template}/route/www.js +6 -6
- package/{web → template}/skeleton/main.html +1 -1
- package/{web → template}/view/content/about.html +5 -5
- package/{web → template}/view/content/home.html +12 -12
- package/template/view/footer/main.html +11 -0
- package/{web → template}/view/head/main.html +1 -1
- package/{web → template}/view/header/main.html +2 -2
- package/test/core/Candy.test.js +58 -58
- package/test/core/Commands.test.js +7 -7
- package/test/core/Config.test.js +82 -85
- package/test/core/Lang.test.js +2 -2
- package/test/core/Process.test.js +6 -6
- package/test/framework/Route.test.js +56 -37
- package/test/framework/View/EarlyHints.test.js +2 -2
- package/test/framework/WebSocket.test.js +100 -0
- package/test/framework/middleware.test.js +85 -0
- package/test/server/Api.test.js +31 -31
- package/test/server/DNS.test.js +11 -11
- package/test/server/Hub.test.js +497 -0
- package/test/server/Mail.account.test_.js +3 -3
- package/test/server/Mail.init.test_.js +10 -10
- package/test/server/Mail.test_.js +20 -20
- package/test/server/SSL.test_.js +54 -54
- package/test/server/Server.test.js +39 -39
- package/test/server/Service.test_.js +7 -7
- package/test/server/Subdomain.test.js +7 -7
- package/test/server/Web/Firewall.test.js +87 -87
- package/test/server/Web/Proxy.test.js +397 -0
- package/test/server/{Web.test_.js → Web.test.js} +137 -205
- package/test/server/__mocks__/fs.js +2 -2
- package/test/server/__mocks__/{globalCandy.js → globalOdac.js} +5 -5
- package/test/server/__mocks__/index.js +6 -6
- package/test/server/__mocks__/testFactories.js +1 -1
- package/test/server/__mocks__/testHelpers.js +7 -7
- package/.husky/pre-commit +0 -2
- package/.kiro/steering/code-style.md +0 -56
- package/.kiro/steering/product.md +0 -20
- package/.kiro/steering/structure.md +0 -77
- package/.kiro/steering/tech.md +0 -87
- package/AGENTS.md +0 -84
- package/bin/candy +0 -10
- package/bin/candypack +0 -10
- package/cli/index.js +0 -3
- package/cli/src/Cli.js +0 -348
- package/cli/src/Connector.js +0 -93
- package/cli/src/Monitor.js +0 -416
- package/core/Candy.js +0 -87
- package/core/Commands.js +0 -239
- package/core/Config.js +0 -1094
- package/core/Lang.js +0 -52
- package/core/Log.js +0 -43
- package/core/Process.js +0 -26
- package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +0 -20
- package/docs/server/01-installation/01-quick-install.md +0 -19
- package/docs/server/01-installation/02-manual-installation-via-npm.md +0 -9
- package/docs/server/02-get-started/01-core-concepts.md +0 -7
- package/docs/server/02-get-started/02-basic-commands.md +0 -57
- package/docs/server/02-get-started/03-cli-reference.md +0 -276
- package/docs/server/02-get-started/04-cli-quick-reference.md +0 -102
- package/docs/server/03-service/01-start-a-new-service.md +0 -57
- package/docs/server/03-service/02-delete-a-service.md +0 -48
- package/docs/server/04-web/01-create-a-website.md +0 -36
- package/docs/server/04-web/02-list-websites.md +0 -9
- package/docs/server/04-web/03-delete-a-website.md +0 -29
- package/docs/server/05-subdomain/01-create-a-subdomain.md +0 -32
- package/docs/server/05-subdomain/02-list-subdomains.md +0 -33
- package/docs/server/05-subdomain/03-delete-a-subdomain.md +0 -41
- package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +0 -34
- package/docs/server/07-mail/01-create-a-mail-account.md +0 -23
- package/docs/server/07-mail/02-delete-a-mail-account.md +0 -20
- package/docs/server/07-mail/03-list-mail-accounts.md +0 -20
- package/docs/server/07-mail/04-change-account-password.md +0 -23
- package/framework/src/Candy.js +0 -81
- package/framework/src/Route.js +0 -455
- package/framework/src/Server.js +0 -15
- package/locale/de-DE.json +0 -80
- package/locale/en-US.json +0 -79
- package/locale/es-ES.json +0 -80
- package/locale/fr-FR.json +0 -80
- package/locale/pt-BR.json +0 -80
- package/locale/ru-RU.json +0 -80
- package/locale/tr-TR.json +0 -85
- package/locale/zh-CN.json +0 -80
- package/server/index.js +0 -5
- package/server/src/Api.js +0 -88
- package/server/src/DNS.js +0 -940
- package/server/src/Hub.js +0 -535
- package/server/src/Mail.js +0 -571
- package/server/src/SSL.js +0 -180
- package/server/src/Server.js +0 -27
- package/server/src/Service.js +0 -248
- package/server/src/Subdomain.js +0 -64
- package/server/src/Web/Firewall.js +0 -170
- package/server/src/Web/Proxy.js +0 -134
- package/server/src/Web.js +0 -451
- package/server/src/mail/imap.js +0 -1091
- package/server/src/mail/server.js +0 -32
- package/server/src/mail/smtp.js +0 -786
- package/test/server/Client.test.js +0 -338
- package/test/server/__mocks__/http-proxy.js +0 -105
- package/watchdog/index.js +0 -3
- package/watchdog/src/Watchdog.js +0 -156
- package/web/config.json +0 -5
- package/web/view/footer/main.html +0 -11
- /package/{framework/src → src}/Env.js +0 -0
- /package/{framework/src → src}/Route/Cron.js +0 -0
- /package/{framework/src → src}/Token.js +0 -0
|
@@ -5,13 +5,13 @@ Forms can automatically insert data into your database without writing any contr
|
|
|
5
5
|
## Basic Usage
|
|
6
6
|
|
|
7
7
|
```html
|
|
8
|
-
<
|
|
9
|
-
<
|
|
10
|
-
<
|
|
11
|
-
</
|
|
8
|
+
<odac:form table="waitlist">
|
|
9
|
+
<odac:field name="email" type="email" label="Email">
|
|
10
|
+
<odac:validate rule="required|email|unique"/>
|
|
11
|
+
</odac:field>
|
|
12
12
|
|
|
13
|
-
<
|
|
14
|
-
</
|
|
13
|
+
<odac:submit text="Join"/>
|
|
14
|
+
</odac:form>
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
That's it! The form will automatically:
|
|
@@ -44,21 +44,21 @@ CREATE TABLE `waitlist` (
|
|
|
44
44
|
<div class="waitlist-page">
|
|
45
45
|
<h1>Join Our Waitlist</h1>
|
|
46
46
|
|
|
47
|
-
<
|
|
48
|
-
<
|
|
49
|
-
<
|
|
50
|
-
</
|
|
47
|
+
<odac:form table="waitlist" redirect="/" success="Thank you for joining!">
|
|
48
|
+
<odac:field name="email" type="email" label="Email" placeholder="your@email.com">
|
|
49
|
+
<odac:validate rule="required|email|unique" message="Please enter a valid email"/>
|
|
50
|
+
</odac:field>
|
|
51
51
|
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
</
|
|
52
|
+
<odac:field name="name" type="text" label="Name" placeholder="Your name">
|
|
53
|
+
<odac:validate rule="required|minlen:2" message="Name is required"/>
|
|
54
|
+
</odac:field>
|
|
55
55
|
|
|
56
|
-
<
|
|
57
|
-
<
|
|
58
|
-
<
|
|
56
|
+
<odac:set name="created_at" compute="now"/>
|
|
57
|
+
<odac:set name="ip" compute="ip"/>
|
|
58
|
+
<odac:set name="user_agent" compute="user_agent"/>
|
|
59
59
|
|
|
60
|
-
<
|
|
61
|
-
</
|
|
60
|
+
<odac:submit text="Join Waitlist" loading="Joining..." class="btn btn-primary"/>
|
|
61
|
+
</odac:form>
|
|
62
62
|
</div>
|
|
63
63
|
```
|
|
64
64
|
|
|
@@ -66,10 +66,10 @@ CREATE TABLE `waitlist` (
|
|
|
66
66
|
|
|
67
67
|
**controller/waitlist.js**
|
|
68
68
|
```javascript
|
|
69
|
-
module.exports =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
module.exports = Odac => {
|
|
70
|
+
Odac.View.skeleton('default')
|
|
71
|
+
Odac.View.set({content: 'waitlist'})
|
|
72
|
+
Odac.View.print()
|
|
73
73
|
}
|
|
74
74
|
```
|
|
75
75
|
|
|
@@ -77,7 +77,7 @@ module.exports = Candy => {
|
|
|
77
77
|
|
|
78
78
|
**route/www.js**
|
|
79
79
|
```javascript
|
|
80
|
-
|
|
80
|
+
Odac.Route.page('/waitlist', 'waitlist')
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
Done! No form submission handler needed.
|
|
@@ -88,21 +88,21 @@ Done! No form submission handler needed.
|
|
|
88
88
|
Database table name where data will be inserted.
|
|
89
89
|
|
|
90
90
|
```html
|
|
91
|
-
<
|
|
91
|
+
<odac:form table="newsletter_subscribers">
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
### `redirect` (optional)
|
|
95
95
|
URL to redirect after successful submission.
|
|
96
96
|
|
|
97
97
|
```html
|
|
98
|
-
<
|
|
98
|
+
<odac:form table="waitlist" redirect="/thank-you">
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
### `success` (optional)
|
|
102
102
|
Custom success message to display.
|
|
103
103
|
|
|
104
104
|
```html
|
|
105
|
-
<
|
|
105
|
+
<odac:form table="waitlist" success="Welcome! We'll notify you soon.">
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
## Unique Validation
|
|
@@ -110,9 +110,9 @@ Custom success message to display.
|
|
|
110
110
|
Use `unique` rule to prevent duplicate entries:
|
|
111
111
|
|
|
112
112
|
```html
|
|
113
|
-
<
|
|
114
|
-
<
|
|
115
|
-
</
|
|
113
|
+
<odac:field name="email" type="email">
|
|
114
|
+
<odac:validate rule="required|email|unique" message="This email is already registered"/>
|
|
115
|
+
</odac:field>
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
The system will:
|
|
@@ -122,13 +122,13 @@ The system will:
|
|
|
122
122
|
|
|
123
123
|
## Auto-Set Values
|
|
124
124
|
|
|
125
|
-
Use `<
|
|
125
|
+
Use `<odac:set>` to automatically populate fields:
|
|
126
126
|
|
|
127
127
|
```html
|
|
128
|
-
<
|
|
129
|
-
<
|
|
130
|
-
<
|
|
131
|
-
<
|
|
128
|
+
<odac:set name="created_at" compute="now"/>
|
|
129
|
+
<odac:set name="ip" compute="ip"/>
|
|
130
|
+
<odac:set name="user_agent" compute="user_agent"/>
|
|
131
|
+
<odac:set name="status" value="pending"/>
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
### Compute Types
|
|
@@ -144,9 +144,9 @@ Use `<candy:set>` to automatically populate fields:
|
|
|
144
144
|
### Static Values
|
|
145
145
|
|
|
146
146
|
```html
|
|
147
|
-
<
|
|
148
|
-
<
|
|
149
|
-
<
|
|
147
|
+
<odac:set name="status" value="pending"/>
|
|
148
|
+
<odac:set name="source" value="website"/>
|
|
149
|
+
<odac:set name="plan" value="free"/>
|
|
150
150
|
```
|
|
151
151
|
|
|
152
152
|
### Conditional Set
|
|
@@ -154,7 +154,7 @@ Use `<candy:set>` to automatically populate fields:
|
|
|
154
154
|
Only set if field is empty:
|
|
155
155
|
|
|
156
156
|
```html
|
|
157
|
-
<
|
|
157
|
+
<odac:set name="country" value="US" if-empty/>
|
|
158
158
|
```
|
|
159
159
|
|
|
160
160
|
## Use Cases
|
|
@@ -162,58 +162,58 @@ Only set if field is empty:
|
|
|
162
162
|
### Newsletter Signup
|
|
163
163
|
|
|
164
164
|
```html
|
|
165
|
-
<
|
|
166
|
-
<
|
|
167
|
-
<
|
|
168
|
-
</
|
|
165
|
+
<odac:form table="newsletter" success="Thanks for subscribing!">
|
|
166
|
+
<odac:field name="email" type="email">
|
|
167
|
+
<odac:validate rule="required|email|unique"/>
|
|
168
|
+
</odac:field>
|
|
169
169
|
|
|
170
|
-
<
|
|
171
|
-
<
|
|
170
|
+
<odac:set name="subscribed_at" compute="now"/>
|
|
171
|
+
<odac:set name="status" value="active"/>
|
|
172
172
|
|
|
173
|
-
<
|
|
174
|
-
</
|
|
173
|
+
<odac:submit text="Subscribe"/>
|
|
174
|
+
</odac:form>
|
|
175
175
|
```
|
|
176
176
|
|
|
177
177
|
### Feedback Form
|
|
178
178
|
|
|
179
179
|
```html
|
|
180
|
-
<
|
|
181
|
-
<
|
|
182
|
-
<
|
|
183
|
-
</
|
|
180
|
+
<odac:form table="feedback" redirect="/" success="Thank you for your feedback!">
|
|
181
|
+
<odac:field name="rating" type="number" label="Rating (1-5)">
|
|
182
|
+
<odac:validate rule="required|min:1|max:5"/>
|
|
183
|
+
</odac:field>
|
|
184
184
|
|
|
185
|
-
<
|
|
186
|
-
<
|
|
187
|
-
</
|
|
185
|
+
<odac:field name="comment" type="textarea" label="Comment">
|
|
186
|
+
<odac:validate rule="required|minlen:10"/>
|
|
187
|
+
</odac:field>
|
|
188
188
|
|
|
189
|
-
<
|
|
190
|
-
<
|
|
189
|
+
<odac:set name="created_at" compute="now"/>
|
|
190
|
+
<odac:set name="ip" compute="ip"/>
|
|
191
191
|
|
|
192
|
-
<
|
|
193
|
-
</
|
|
192
|
+
<odac:submit text="Submit Feedback"/>
|
|
193
|
+
</odac:form>
|
|
194
194
|
```
|
|
195
195
|
|
|
196
196
|
### Beta Access Request
|
|
197
197
|
|
|
198
198
|
```html
|
|
199
|
-
<
|
|
200
|
-
<
|
|
201
|
-
<
|
|
202
|
-
</
|
|
199
|
+
<odac:form table="beta_requests" success="You're on the list!">
|
|
200
|
+
<odac:field name="email" type="email">
|
|
201
|
+
<odac:validate rule="required|email|unique"/>
|
|
202
|
+
</odac:field>
|
|
203
203
|
|
|
204
|
-
<
|
|
205
|
-
<
|
|
206
|
-
</
|
|
204
|
+
<odac:field name="company" type="text">
|
|
205
|
+
<odac:validate rule="required"/>
|
|
206
|
+
</odac:field>
|
|
207
207
|
|
|
208
|
-
<
|
|
209
|
-
<
|
|
210
|
-
</
|
|
208
|
+
<odac:field name="use_case" type="textarea">
|
|
209
|
+
<odac:validate rule="required|minlen:20"/>
|
|
210
|
+
</odac:field>
|
|
211
211
|
|
|
212
|
-
<
|
|
213
|
-
<
|
|
212
|
+
<odac:set name="requested_at" compute="now"/>
|
|
213
|
+
<odac:set name="status" value="pending"/>
|
|
214
214
|
|
|
215
|
-
<
|
|
216
|
-
</
|
|
215
|
+
<odac:submit text="Request Access"/>
|
|
216
|
+
</odac:form>
|
|
217
217
|
```
|
|
218
218
|
|
|
219
219
|
## Error Handling
|
|
@@ -255,37 +255,37 @@ Automatic DB insert includes all security features:
|
|
|
255
255
|
|
|
256
256
|
## Combining with Custom Logic
|
|
257
257
|
|
|
258
|
-
You can add custom logic by specifying a custom `action` attribute. When you do this, the form data is validated and prepared, but the automatic DB insert is skipped. Instead, your controller receives the validated data via `
|
|
258
|
+
You can add custom logic by specifying a custom `action` attribute. When you do this, the form data is validated and prepared, but the automatic DB insert is skipped. Instead, your controller receives the validated data via `Odac.formData`:
|
|
259
259
|
|
|
260
260
|
```javascript
|
|
261
261
|
// In your view:
|
|
262
|
-
// <
|
|
262
|
+
// <odac:form action="/contact/submit" table="contacts">
|
|
263
263
|
|
|
264
264
|
// In your controller:
|
|
265
|
-
|
|
266
|
-
//
|
|
267
|
-
//
|
|
265
|
+
Odac.Route.post('/contact/submit', async Odac => {
|
|
266
|
+
// Odac.formData contains validated form data
|
|
267
|
+
// Odac.formConfig contains form configuration
|
|
268
268
|
|
|
269
269
|
// Perform custom logic (send email, call API, etc.)
|
|
270
|
-
await sendEmail(
|
|
270
|
+
await sendEmail(Odac.formData.email, 'Thank you!')
|
|
271
271
|
|
|
272
272
|
// Manually insert to database if needed
|
|
273
|
-
await
|
|
273
|
+
await Odac.Mysql.query('INSERT INTO contacts SET ?', Odac.formData)
|
|
274
274
|
|
|
275
|
-
return
|
|
275
|
+
return Odac.return({
|
|
276
276
|
result: {success: true, message: 'Message sent!'}
|
|
277
277
|
})
|
|
278
278
|
})
|
|
279
|
-
const data =
|
|
279
|
+
const data = Odac.formData
|
|
280
280
|
|
|
281
281
|
// Send welcome email
|
|
282
|
-
|
|
282
|
+
Odac.Mail()
|
|
283
283
|
.to(data.email)
|
|
284
284
|
.subject('Welcome!')
|
|
285
285
|
.send('Thanks for joining!')
|
|
286
286
|
|
|
287
287
|
// Return custom response
|
|
288
|
-
return
|
|
288
|
+
return Odac.return({
|
|
289
289
|
result: {
|
|
290
290
|
success: true,
|
|
291
291
|
message: 'Check your email!'
|
package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
## 📥 The Request Object
|
|
2
2
|
|
|
3
|
-
The `
|
|
3
|
+
The `Odac.Request` object contains information about the user's incoming request.
|
|
4
4
|
|
|
5
5
|
### Getting Request Parameters
|
|
6
6
|
|
|
7
|
-
#### Using
|
|
7
|
+
#### Using Odac.request() (Recommended)
|
|
8
8
|
|
|
9
|
-
The easiest way to get request parameters is using `
|
|
9
|
+
The easiest way to get request parameters is using `Odac.request()`:
|
|
10
10
|
|
|
11
11
|
```javascript
|
|
12
|
-
module.exports = async function (
|
|
12
|
+
module.exports = async function (Odac) {
|
|
13
13
|
// Get parameter from GET or POST automatically
|
|
14
|
-
const userName = await
|
|
15
|
-
const userId = await
|
|
14
|
+
const userName = await Odac.request('name')
|
|
15
|
+
const userId = await Odac.request('id')
|
|
16
16
|
|
|
17
17
|
return `Hello ${userName}!`
|
|
18
18
|
}
|
|
@@ -21,12 +21,12 @@ module.exports = async function (Candy) {
|
|
|
21
21
|
**Specify Method (Optional):**
|
|
22
22
|
|
|
23
23
|
```javascript
|
|
24
|
-
module.exports = async function (
|
|
24
|
+
module.exports = async function (Odac) {
|
|
25
25
|
// Get from GET parameters only
|
|
26
|
-
const searchQuery = await
|
|
26
|
+
const searchQuery = await Odac.request('q', 'GET')
|
|
27
27
|
|
|
28
28
|
// Get from POST parameters only
|
|
29
|
-
const formName = await
|
|
29
|
+
const formName = await Odac.request('name', 'POST')
|
|
30
30
|
|
|
31
31
|
return `Searching for: ${searchQuery}`
|
|
32
32
|
}
|
|
@@ -37,12 +37,12 @@ module.exports = async function (Candy) {
|
|
|
37
37
|
You can also access request data directly:
|
|
38
38
|
|
|
39
39
|
```javascript
|
|
40
|
-
module.exports = function (
|
|
40
|
+
module.exports = function (Odac) {
|
|
41
41
|
// GET parameters (URL query string like ?id=123)
|
|
42
|
-
const userId =
|
|
42
|
+
const userId = Odac.Request.get('id')
|
|
43
43
|
|
|
44
44
|
// POST parameters (form data)
|
|
45
|
-
const userName =
|
|
45
|
+
const userName = Odac.Request.post('name')
|
|
46
46
|
|
|
47
47
|
return `User: ${userName}`
|
|
48
48
|
}
|
|
@@ -50,18 +50,18 @@ module.exports = function (Candy) {
|
|
|
50
50
|
|
|
51
51
|
### Request Properties
|
|
52
52
|
|
|
53
|
-
* `
|
|
54
|
-
* `
|
|
55
|
-
* `
|
|
56
|
-
* `
|
|
57
|
-
* `
|
|
53
|
+
* `Odac.Request.method` - HTTP method ('GET', 'POST', etc.)
|
|
54
|
+
* `Odac.Request.url` - Full URL the user visited
|
|
55
|
+
* `Odac.Request.host` - Website's hostname
|
|
56
|
+
* `Odac.Request.ip` - User's IP address
|
|
57
|
+
* `Odac.Request.ssl` - Whether connection is SSL/HTTPS
|
|
58
58
|
|
|
59
59
|
### Request Headers
|
|
60
60
|
|
|
61
61
|
```javascript
|
|
62
|
-
module.exports = function (
|
|
63
|
-
const userAgent =
|
|
64
|
-
const contentType =
|
|
62
|
+
module.exports = function (Odac) {
|
|
63
|
+
const userAgent = Odac.Request.header('user-agent')
|
|
64
|
+
const contentType = Odac.Request.header('content-type')
|
|
65
65
|
|
|
66
66
|
return `Browser: ${userAgent}`
|
|
67
67
|
}
|
|
@@ -70,25 +70,25 @@ module.exports = function (Candy) {
|
|
|
70
70
|
### Complete Example
|
|
71
71
|
|
|
72
72
|
```javascript
|
|
73
|
-
module.exports = async function (
|
|
73
|
+
module.exports = async function (Odac) {
|
|
74
74
|
// Get request parameters
|
|
75
|
-
const productId = await
|
|
76
|
-
const quantity = await
|
|
75
|
+
const productId = await Odac.request('id')
|
|
76
|
+
const quantity = await Odac.request('quantity') || 1
|
|
77
77
|
|
|
78
78
|
// Check request method
|
|
79
|
-
if (
|
|
79
|
+
if (Odac.Request.method === 'POST') {
|
|
80
80
|
// Handle form submission
|
|
81
81
|
const result = await processOrder(productId, quantity)
|
|
82
82
|
return { success: true, orderId: result.id }
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// Show product page
|
|
86
|
-
|
|
86
|
+
Odac.set({
|
|
87
87
|
productId: productId,
|
|
88
88
|
quantity: quantity
|
|
89
89
|
})
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
Odac.View.set({
|
|
92
92
|
skeleton: 'main',
|
|
93
93
|
content: 'product.detail'
|
|
94
94
|
})
|
|
@@ -4,37 +4,37 @@ Once you've processed the request, it's time to send something back. You've got
|
|
|
4
4
|
|
|
5
5
|
#### The Simple Way: Just Return It!
|
|
6
6
|
|
|
7
|
-
For many cases, you can just `return` a value from your controller.
|
|
7
|
+
For many cases, you can just `return` a value from your controller. Odac is smart enough to figure out what to do.
|
|
8
8
|
|
|
9
9
|
```javascript
|
|
10
10
|
// Return some HTML
|
|
11
|
-
module.exports = function (
|
|
11
|
+
module.exports = function (Odac) {
|
|
12
12
|
return '<h1>Welcome to the site!</h1>';
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// Return some JSON for an API
|
|
16
|
-
module.exports = function (
|
|
16
|
+
module.exports = function (Odac) {
|
|
17
17
|
return { status: 'success', message: 'Your data was saved!' };
|
|
18
18
|
}
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
#### The Helper Functions: More Control
|
|
22
22
|
|
|
23
|
-
Need a bit more control? The `
|
|
23
|
+
Need a bit more control? The `Odac` object has your back.
|
|
24
24
|
|
|
25
|
-
* `
|
|
26
|
-
* `
|
|
25
|
+
* `Odac.return(data)`: Does the same thing as a direct return, but you can call it from anywhere in your function. It stops everything and sends the response immediately.
|
|
26
|
+
* `Odac.direct(url)`: Need to send the user to a different page? This function performs a redirect, telling the user's browser to go to a new URL.
|
|
27
27
|
|
|
28
28
|
**Example:**
|
|
29
29
|
```javascript
|
|
30
|
-
module.exports = function (
|
|
30
|
+
module.exports = function (Odac) {
|
|
31
31
|
// If the user isn't logged in...
|
|
32
|
-
if (!
|
|
32
|
+
if (!Odac.Auth.isLogin()) {
|
|
33
33
|
// ...send them to the login page!
|
|
34
|
-
return
|
|
34
|
+
return Odac.direct('/login');
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// Otherwise, give them their data.
|
|
38
|
-
|
|
38
|
+
Odac.return({ data: 'here is your secret stuff' });
|
|
39
39
|
}
|
|
40
40
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## 📁 View System Overview
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Odac's view system creates dynamic HTML pages by combining skeleton (layout) and view (content) files. This system provides a modular structure by keeping page layout and content separate.
|
|
4
4
|
|
|
5
5
|
### Directory Structure
|
|
6
6
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
## 🎨 Rendering Views
|
|
2
2
|
|
|
3
|
-
In
|
|
3
|
+
In Odac, you use the `Odac.View` object to render views. There are two main approaches:
|
|
4
4
|
|
|
5
5
|
### 1. Combining Skeleton and View Parts
|
|
6
6
|
|
|
7
7
|
The most common usage is to select a skeleton and place view parts into it.
|
|
8
8
|
|
|
9
9
|
```javascript
|
|
10
|
-
module.exports = function (
|
|
11
|
-
|
|
10
|
+
module.exports = function (Odac) {
|
|
11
|
+
Odac.View
|
|
12
12
|
.skeleton('main') // Use skeleton/main.html
|
|
13
13
|
.set('header', 'main') // Place view/header/main.html into {{ HEADER }}
|
|
14
14
|
.set('content', 'home') // Place view/content/home.html into {{ CONTENT }}
|
|
@@ -21,8 +21,8 @@ module.exports = function (Candy) {
|
|
|
21
21
|
You can set all view parts at once:
|
|
22
22
|
|
|
23
23
|
```javascript
|
|
24
|
-
module.exports = function (
|
|
25
|
-
|
|
24
|
+
module.exports = function (Odac) {
|
|
25
|
+
Odac.View.set({
|
|
26
26
|
skeleton: 'main',
|
|
27
27
|
header: 'main',
|
|
28
28
|
content: 'home',
|
|
@@ -36,7 +36,7 @@ module.exports = function (Candy) {
|
|
|
36
36
|
View files can be organized in subdirectories. You can access them using dot notation:
|
|
37
37
|
|
|
38
38
|
```javascript
|
|
39
|
-
|
|
39
|
+
Odac.View.set({
|
|
40
40
|
skeleton: 'dashboard',
|
|
41
41
|
header: 'dashboard.main', // view/header/dashboard/main.html
|
|
42
42
|
sidebar: 'dashboard.menu', // view/sidebar/dashboard/menu.html
|
|
@@ -50,7 +50,7 @@ You can render views directly from route files without using a controller:
|
|
|
50
50
|
|
|
51
51
|
```javascript
|
|
52
52
|
// route/www.js
|
|
53
|
-
|
|
53
|
+
Odac.Route.page('/about').view({
|
|
54
54
|
skeleton: 'main',
|
|
55
55
|
header: 'main',
|
|
56
56
|
content: 'about',
|
|
@@ -63,7 +63,7 @@ Candy.Route.page('/about').view({
|
|
|
63
63
|
If you're using the same directory structure for all placeholders, you can use the `all()` method:
|
|
64
64
|
|
|
65
65
|
```javascript
|
|
66
|
-
|
|
66
|
+
Odac.View
|
|
67
67
|
.skeleton('main')
|
|
68
68
|
.all('home') // view/home/header.html, view/home/content.html, view/home/footer.html
|
|
69
69
|
```
|
|
@@ -108,27 +108,27 @@ Create a separate view part for the `<head>` section:
|
|
|
108
108
|
<head>
|
|
109
109
|
<meta charset="UTF-8">
|
|
110
110
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
111
|
-
<title>{{
|
|
112
|
-
<meta name="description" content="{{
|
|
111
|
+
<title>{{ Odac.pageTitle }}</title>
|
|
112
|
+
<meta name="description" content="{{ Odac.pageDescription }}">
|
|
113
113
|
<link rel="stylesheet" href="/assets/css/style.css">
|
|
114
114
|
</head>
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
**Controller:**
|
|
118
118
|
```javascript
|
|
119
|
-
module.exports = async function (
|
|
120
|
-
const productId =
|
|
121
|
-
const product = await
|
|
119
|
+
module.exports = async function (Odac) {
|
|
120
|
+
const productId = Odac.Request.get('id')
|
|
121
|
+
const product = await Odac.Mysql.table('products')
|
|
122
122
|
.where('id', productId)
|
|
123
123
|
.first()
|
|
124
124
|
|
|
125
125
|
// Set dynamic title and description
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
Odac.pageTitle = product ? `${product.name} - My Store` : 'Product Not Found'
|
|
127
|
+
Odac.pageDescription = product ? product.short_description : ''
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
Odac.product = product
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
Odac.View.set({
|
|
132
132
|
skeleton: 'main',
|
|
133
133
|
head: 'main', // Include dynamic head
|
|
134
134
|
header: 'main',
|
|
@@ -159,11 +159,11 @@ Include the title tag in your content view:
|
|
|
159
159
|
|
|
160
160
|
**Content View (view/content/product.html):**
|
|
161
161
|
```html
|
|
162
|
-
<title>{{
|
|
162
|
+
<title>{{ Odac.product.name }} - My Store</title>
|
|
163
163
|
|
|
164
164
|
<div class="product">
|
|
165
|
-
<h1>{{
|
|
166
|
-
<p>{{
|
|
165
|
+
<h1>{{ Odac.product.name }}</h1>
|
|
166
|
+
<p>{{ Odac.product.description }}</p>
|
|
167
167
|
</div>
|
|
168
168
|
```
|
|
169
169
|
|
|
@@ -175,5 +175,5 @@ Include the title tag in your content view:
|
|
|
175
175
|
- Skeleton files should be in the `skeleton/` directory, view files in the `view/` directory
|
|
176
176
|
- Placeholders for view parts are written in uppercase: `{{ HEADER }}`, `{{ CONTENT }}`, etc.
|
|
177
177
|
- View part names are specified in lowercase: `header`, `content`, etc.
|
|
178
|
-
- Variables in skeleton/views are accessed via `
|
|
179
|
-
- You don't need to use `return` from the controller, `
|
|
178
|
+
- Variables in skeleton/views are accessed via `Odac` object: `{{ Odac.variableName }}`
|
|
179
|
+
- You don't need to use `return` from the controller, `Odac.View.set()` automatically initiates the rendering process
|