odac 1.0.1 → 1.2.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/.agent/rules/coding.md +27 -0
- package/.agent/rules/memory.md +33 -0
- package/.agent/rules/project.md +30 -0
- package/.agent/rules/workflow.md +16 -0
- package/.github/workflows/auto-pr-description.yml +3 -1
- package/.github/workflows/release.yml +42 -1
- package/.github/workflows/test-coverage.yml +6 -5
- package/.github/workflows/test-publish.yml +36 -0
- package/.husky/pre-commit +10 -0
- package/.husky/pre-push +13 -0
- package/.releaserc.js +3 -3
- package/CHANGELOG.md +184 -0
- package/README.md +53 -34
- package/bin/odac.js +181 -49
- package/client/odac.js +878 -995
- package/docs/backend/01-overview/03-development-server.md +39 -46
- package/docs/backend/02-structure/01-typical-project-layout.md +59 -25
- package/docs/backend/03-config/00-configuration-overview.md +15 -6
- package/docs/backend/03-config/01-database-connection.md +3 -3
- package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
- package/docs/backend/03-config/03-request-timeout.md +1 -1
- package/docs/backend/03-config/04-environment-variables.md +4 -4
- package/docs/backend/03-config/05-early-hints.md +2 -2
- package/docs/backend/04-routing/02-controller-less-view-routes.md +9 -3
- package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
- package/docs/backend/04-routing/07-cron-jobs.md +17 -1
- package/docs/backend/04-routing/09-websocket.md +29 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
- package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +2 -0
- package/docs/backend/05-controllers/03-controller-classes.md +61 -55
- package/docs/backend/05-forms/01-custom-forms.md +103 -95
- package/docs/backend/05-forms/02-automatic-database-insert.md +21 -21
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
- package/docs/backend/07-views/02-rendering-a-view.md +1 -1
- package/docs/backend/07-views/03-variables.md +5 -5
- package/docs/backend/07-views/04-request-data.md +1 -1
- package/docs/backend/07-views/08-backend-javascript.md +1 -1
- package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
- package/docs/backend/08-database/01-getting-started.md +100 -0
- package/docs/backend/08-database/02-basics.md +136 -0
- package/docs/backend/08-database/03-advanced.md +84 -0
- package/docs/backend/08-database/04-migrations.md +48 -0
- package/docs/backend/09-validation/01-the-validator-service.md +1 -0
- package/docs/backend/10-authentication/03-register.md +9 -2
- package/docs/backend/10-authentication/04-odac-register-forms.md +48 -48
- package/docs/backend/10-authentication/05-session-management.md +16 -2
- package/docs/backend/10-authentication/06-odac-login-forms.md +50 -50
- package/docs/backend/10-authentication/07-magic-links.md +134 -0
- package/docs/backend/11-mail/01-the-mail-service.md +118 -28
- package/docs/backend/12-streaming/01-streaming-overview.md +2 -2
- package/docs/backend/13-utilities/01-odac-var.md +7 -7
- package/docs/backend/13-utilities/02-ipc.md +73 -0
- package/docs/frontend/01-overview/01-introduction.md +5 -1
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +1 -1
- package/docs/index.json +21 -125
- package/eslint.config.mjs +5 -47
- package/jest.config.js +1 -1
- package/package.json +16 -7
- package/src/Auth.js +414 -121
- package/src/Config.js +12 -7
- package/src/Database.js +188 -0
- package/src/Env.js +3 -1
- package/src/Ipc.js +337 -0
- package/src/Lang.js +9 -2
- package/src/Mail.js +408 -37
- package/src/Odac.js +105 -40
- package/src/Request.js +71 -49
- package/src/Route/Cron.js +62 -18
- package/src/Route/Internal.js +215 -12
- package/src/Route/Middleware.js +7 -2
- package/src/Route.js +372 -109
- package/src/Server.js +118 -12
- package/src/Storage.js +169 -0
- package/src/Token.js +6 -4
- package/src/Validator.js +95 -3
- package/src/Var.js +22 -6
- package/src/View/EarlyHints.js +43 -33
- package/src/View/Form.js +210 -28
- package/src/View.js +108 -7
- package/src/WebSocket.js +18 -3
- package/template/odac.json +5 -0
- package/template/package.json +3 -1
- package/template/route/www.js +12 -10
- package/template/view/content/home.html +3 -3
- package/template/view/head/main.html +2 -2
- package/test/Client.test.js +168 -0
- package/test/Config.test.js +112 -0
- package/test/Lang.test.js +92 -0
- package/test/Odac.test.js +86 -0
- package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
- package/test/{framework/Route.test.js → Route.test.js} +1 -1
- package/test/{framework/View → View}/EarlyHints.test.js +1 -1
- package/test/{framework/WebSocket.test.js → WebSocket.test.js} +2 -2
- package/test/scripts/check-coverage.js +4 -4
- package/docs/backend/08-database/01-database-connection.md +0 -99
- package/docs/backend/08-database/02-using-mysql.md +0 -322
- package/src/Mysql.js +0 -575
- package/template/config.json +0 -5
- package/test/cli/Cli.test.js +0 -36
- package/test/core/Candy.test.js +0 -234
- package/test/core/Commands.test.js +0 -538
- package/test/core/Config.test.js +0 -1432
- package/test/core/Lang.test.js +0 -250
- package/test/core/Process.test.js +0 -156
- package/test/server/Api.test.js +0 -647
- package/test/server/DNS.test.js +0 -2050
- package/test/server/DNS.test.js.bak +0 -2084
- package/test/server/Hub.test.js +0 -497
- package/test/server/Log.test.js +0 -73
- package/test/server/Mail.account.test_.js +0 -460
- package/test/server/Mail.init.test_.js +0 -411
- package/test/server/Mail.test_.js +0 -1340
- package/test/server/SSL.test_.js +0 -1491
- package/test/server/Server.test.js +0 -765
- package/test/server/Service.test_.js +0 -1127
- package/test/server/Subdomain.test.js +0 -440
- package/test/server/Web/Firewall.test.js +0 -175
- package/test/server/Web/Proxy.test.js +0 -397
- package/test/server/Web.test.js +0 -1494
- package/test/server/__mocks__/acme-client.js +0 -17
- package/test/server/__mocks__/bcrypt.js +0 -50
- package/test/server/__mocks__/child_process.js +0 -389
- package/test/server/__mocks__/crypto.js +0 -432
- package/test/server/__mocks__/fs.js +0 -450
- package/test/server/__mocks__/globalOdac.js +0 -227
- package/test/server/__mocks__/http.js +0 -575
- package/test/server/__mocks__/https.js +0 -272
- package/test/server/__mocks__/index.js +0 -249
- package/test/server/__mocks__/mail/server.js +0 -100
- package/test/server/__mocks__/mail/smtp.js +0 -31
- package/test/server/__mocks__/mailparser.js +0 -81
- package/test/server/__mocks__/net.js +0 -369
- package/test/server/__mocks__/node-forge.js +0 -328
- package/test/server/__mocks__/os.js +0 -320
- package/test/server/__mocks__/path.js +0 -291
- package/test/server/__mocks__/selfsigned.js +0 -8
- package/test/server/__mocks__/server/src/mail/server.js +0 -100
- package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
- package/test/server/__mocks__/smtp-server.js +0 -106
- package/test/server/__mocks__/sqlite3.js +0 -394
- package/test/server/__mocks__/testFactories.js +0 -299
- package/test/server/__mocks__/testHelpers.js +0 -363
- package/test/server/__mocks__/tls.js +0 -229
|
@@ -1,24 +1,46 @@
|
|
|
1
|
-
##
|
|
1
|
+
## 🧩 Service Classes
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
You can organize your business logic into reusable classes. This is especially useful when you want to share logic between multiple methods or keep related functionality together.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
We recommend placing these files in the `class/` directory.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### ❓ Service Class vs. Controller
|
|
8
|
+
|
|
9
|
+
It is important not to confuse **Service Classes** with **Class-Based Controllers**.
|
|
10
|
+
|
|
11
|
+
- **Controllers** (located in `controller/`):
|
|
12
|
+
- Handle HTTP requests (Input -> Process -> Response).
|
|
13
|
+
- Can be defined as Classes for better organization.
|
|
14
|
+
- Are mapped to specific Routes (e.g., via `Route.get()`).
|
|
15
|
+
|
|
16
|
+
- **Service Classes** (located in `class/`):
|
|
17
|
+
- Contain reusable business logic (e.g., `User.calculateReputation()`, `Mail.sendWelcome()`).
|
|
18
|
+
- Are **not** directly mapped to routes.
|
|
19
|
+
- Can be used by *multiple* controllers or other services.
|
|
20
|
+
- Are **Request Scoped**: They are instantiated fresh for every request and attached to the request's `Odac` instance. They correspond to the life-cycle of the request.
|
|
21
|
+
|
|
22
|
+
**Rule of Thumb:** If it talks to the browser/API client, it's a **Controller**. If it processes data behind the scenes, it's a **Service Class**.
|
|
23
|
+
|
|
24
|
+
#### Creating a Service Class
|
|
25
|
+
|
|
26
|
+
Any class file placed in the `class/` directory will be automatically detected. When a request comes in, Odac creates a **new instance** of your class and passes the current request's `Odac` object to the constructor.
|
|
27
|
+
|
|
28
|
+
This means `this.Odac` inside your class gives you access to the specific request, response, authentication state, and database for *that specific user request*.
|
|
8
29
|
|
|
9
30
|
```javascript
|
|
10
|
-
//
|
|
31
|
+
// class/User.js
|
|
11
32
|
class User {
|
|
12
33
|
constructor(Odac) {
|
|
13
34
|
this.Odac = Odac
|
|
14
35
|
}
|
|
15
36
|
|
|
37
|
+
|
|
16
38
|
async getProfile() {
|
|
17
39
|
const user = await this.Odac.Auth.user()
|
|
18
|
-
return
|
|
40
|
+
return {
|
|
19
41
|
success: true,
|
|
20
42
|
user: user
|
|
21
|
-
}
|
|
43
|
+
}
|
|
22
44
|
}
|
|
23
45
|
|
|
24
46
|
async updateProfile() {
|
|
@@ -34,60 +56,44 @@ class User {
|
|
|
34
56
|
const email = await this.Odac.request('email')
|
|
35
57
|
|
|
36
58
|
// Update user in database
|
|
37
|
-
await this.Odac.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
])
|
|
59
|
+
await this.Odac.DB.users.where('id', this.Odac.Auth.user().id).update({
|
|
60
|
+
name: name,
|
|
61
|
+
email: email
|
|
62
|
+
})
|
|
42
63
|
|
|
43
|
-
return
|
|
64
|
+
return {
|
|
44
65
|
success: true,
|
|
45
66
|
message: 'Profile updated successfully'
|
|
46
|
-
}
|
|
67
|
+
}
|
|
47
68
|
}
|
|
48
69
|
}
|
|
49
70
|
|
|
50
71
|
module.exports = User
|
|
51
72
|
```
|
|
52
73
|
|
|
53
|
-
####
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
#### Benefits of Controller Classes
|
|
80
|
-
|
|
81
|
-
- **Organization**: Group related methods together
|
|
82
|
-
- **Reusability**: Share logic between different routes
|
|
83
|
-
- **Maintainability**: Easier to manage complex controllers
|
|
84
|
-
- **Context**: The `Odac` object is always available via `this.Odac`
|
|
85
|
-
|
|
86
|
-
#### Class vs Function Controllers
|
|
87
|
-
|
|
88
|
-
Both approaches work perfectly fine. Use what makes sense for your project:
|
|
89
|
-
|
|
90
|
-
- **Functions**: Great for simple, single-purpose controllers
|
|
91
|
-
- **Classes**: Better for complex logic with multiple related methods
|
|
92
|
-
|
|
93
|
-
The framework automatically detects whether your export is a class or a function and handles it accordingly.
|
|
74
|
+
#### Naming Collisions
|
|
75
|
+
|
|
76
|
+
If your class name conflicts with a built-in Odac service (like `Mail`, `DB`, `Auth`), it will be automatically placed under `Odac.App` namespace to prevent errors.
|
|
77
|
+
|
|
78
|
+
Example: `class/Mail.js` (conflicts with core Mail) -> `Odac.App.Mail`
|
|
79
|
+
|
|
80
|
+
#### Accessing Services in Controllers
|
|
81
|
+
|
|
82
|
+
Since Service classes are attached to the `Odac` instance for each request, you can access them directly by their file name.
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
// controller/get/profile.js
|
|
86
|
+
module.exports = async function (Odac) {
|
|
87
|
+
// Odac.User is a fresh instance of the User class dedicated to this request
|
|
88
|
+
const profile = await Odac.User.getProfile()
|
|
89
|
+
|
|
90
|
+
return Odac.return(profile)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### Benefits of Service Classes
|
|
95
|
+
|
|
96
|
+
- **Organization**: Group related business logic together in `class/`
|
|
97
|
+
- **Reusability**: Share logic between different controllers and routes
|
|
98
|
+
- **Context Awareness**: The `Odac` request object is injected automatically, so your services know about the current user and request.
|
|
99
|
+
- **Separation of Concerns**: Keep your Controllers lightweight by moving heavy logic to Services.
|
|
@@ -5,10 +5,10 @@ Odac provides an automatic form system with built-in validation, CSRF protection
|
|
|
5
5
|
## Basic Usage
|
|
6
6
|
|
|
7
7
|
```html
|
|
8
|
-
<odac:form action="
|
|
9
|
-
<odac:
|
|
8
|
+
<odac:form action="Contact.submit" method="POST">
|
|
9
|
+
<odac:input name="email" type="email" label="Email">
|
|
10
10
|
<odac:validate rule="required|email" message="Valid email required"/>
|
|
11
|
-
</odac:
|
|
11
|
+
</odac:input>
|
|
12
12
|
|
|
13
13
|
<odac:submit text="Send" loading="Sending..."/>
|
|
14
14
|
</odac:form>
|
|
@@ -18,21 +18,23 @@ Odac provides an automatic form system with built-in validation, CSRF protection
|
|
|
18
18
|
|
|
19
19
|
### `<odac:form>`
|
|
20
20
|
|
|
21
|
-
- `action` -
|
|
21
|
+
- `action` - Controller action `Controller.method` (optional if using `table`)
|
|
22
22
|
- `method` - HTTP method (default: POST)
|
|
23
23
|
- `table` - Database table name for automatic insert (optional)
|
|
24
24
|
- `redirect` - Redirect URL after success (optional)
|
|
25
25
|
- `success` - Success message (optional)
|
|
26
26
|
- `class` - Additional CSS classes
|
|
27
27
|
- `id` - Form ID attribute
|
|
28
|
+
- `clear` - Set to `false` to disable auto-clearing inputs on success (optional)
|
|
28
29
|
|
|
29
30
|
```html
|
|
30
|
-
<!-- With custom controller -->
|
|
31
|
-
|
|
31
|
+
<!-- With custom controller action -->
|
|
32
|
+
<!-- Executes 'submit' method in 'contact' controller -->
|
|
33
|
+
<odac:form action="Contact.submit" method="POST" class="my-form" id="contact-form">
|
|
32
34
|
<!-- fields here -->
|
|
33
35
|
</odac:form>
|
|
34
36
|
|
|
35
|
-
<!-- With automatic DB insert -->
|
|
37
|
+
<!-- With automatic DB insert (no controller needed) -->
|
|
36
38
|
<odac:form table="waitlist" redirect="/" success="Thank you for joining!">
|
|
37
39
|
<!-- fields here -->
|
|
38
40
|
</odac:form>
|
|
@@ -40,69 +42,69 @@ Odac provides an automatic form system with built-in validation, CSRF protection
|
|
|
40
42
|
|
|
41
43
|
## Field Types
|
|
42
44
|
|
|
43
|
-
### `<odac:
|
|
45
|
+
### `<odac:input>`
|
|
44
46
|
|
|
45
47
|
Supports all standard HTML input types:
|
|
46
48
|
|
|
47
49
|
```html
|
|
48
50
|
<!-- Text input with multiple validations -->
|
|
49
|
-
<odac:
|
|
51
|
+
<odac:input name="username" type="text" label="Username" placeholder="Enter username">
|
|
50
52
|
<odac:validate rule="required" message="Username is required"/>
|
|
51
53
|
<odac:validate rule="minlen:3" message="Username must be at least 3 characters"/>
|
|
52
54
|
<odac:validate rule="maxlen:20" message="Username cannot exceed 20 characters"/>
|
|
53
55
|
<odac:validate rule="alphanumeric" message="Username can only contain letters and numbers"/>
|
|
54
|
-
</odac:
|
|
56
|
+
</odac:input>
|
|
55
57
|
|
|
56
58
|
<!-- Email input -->
|
|
57
|
-
<odac:
|
|
59
|
+
<odac:input name="email" type="email" label="Email Address" placeholder="your@email.com">
|
|
58
60
|
<odac:validate rule="required" message="Email address is required"/>
|
|
59
61
|
<odac:validate rule="email" message="Please enter a valid email address"/>
|
|
60
62
|
<odac:validate rule="maxlen:100" message="Email is too long"/>
|
|
61
|
-
</odac:
|
|
63
|
+
</odac:input>
|
|
62
64
|
|
|
63
65
|
<!-- Password input with strong validation -->
|
|
64
|
-
<odac:
|
|
66
|
+
<odac:input name="password" type="password" label="Password">
|
|
65
67
|
<odac:validate rule="required" message="Password is required"/>
|
|
66
68
|
<odac:validate rule="minlen:8" message="Password must be at least 8 characters long"/>
|
|
67
69
|
<odac:validate rule="maxlen:50" message="Password is too long"/>
|
|
68
|
-
</odac:
|
|
70
|
+
</odac:input>
|
|
69
71
|
|
|
70
72
|
<!-- Textarea with character limits -->
|
|
71
|
-
<odac:
|
|
73
|
+
<odac:input name="message" type="textarea" label="Your Message" placeholder="Tell us what you think...">
|
|
72
74
|
<odac:validate rule="required" message="Please enter your message"/>
|
|
73
75
|
<odac:validate rule="minlen:10" message="Message must be at least 10 characters"/>
|
|
74
76
|
<odac:validate rule="maxlen:500" message="Message cannot exceed 500 characters"/>
|
|
75
|
-
</odac:
|
|
77
|
+
</odac:input>
|
|
76
78
|
|
|
77
79
|
<!-- Checkbox for terms acceptance -->
|
|
78
|
-
<odac:
|
|
80
|
+
<odac:input name="agree" type="checkbox" label="I agree to the Terms of Service and Privacy Policy">
|
|
79
81
|
<odac:validate rule="accepted" message="You must accept the terms to continue"/>
|
|
80
|
-
</odac:
|
|
82
|
+
</odac:input>
|
|
81
83
|
|
|
82
84
|
<!-- Number input with range -->
|
|
83
|
-
<odac:
|
|
85
|
+
<odac:input name="age" type="number" label="Your Age">
|
|
84
86
|
<odac:validate rule="required" message="Age is required"/>
|
|
85
87
|
<odac:validate rule="min:18" message="You must be at least 18 years old"/>
|
|
86
88
|
<odac:validate rule="max:120" message="Please enter a valid age"/>
|
|
87
|
-
</odac:
|
|
89
|
+
</odac:input>
|
|
88
90
|
|
|
89
91
|
<!-- Phone number -->
|
|
90
|
-
<odac:
|
|
92
|
+
<odac:input name="phone" type="text" label="Phone Number" placeholder="+1 (555) 123-4567">
|
|
91
93
|
<odac:validate rule="required" message="Phone number is required"/>
|
|
92
94
|
<odac:validate rule="minlen:10" message="Phone number must be at least 10 digits"/>
|
|
93
|
-
</odac:
|
|
95
|
+
</odac:input>
|
|
94
96
|
|
|
95
97
|
<!-- URL input -->
|
|
96
|
-
<odac:
|
|
98
|
+
<odac:input name="website" type="url" label="Website" placeholder="https://example.com">
|
|
97
99
|
<odac:validate rule="url" message="Please enter a valid URL"/>
|
|
98
|
-
</odac:
|
|
100
|
+
</odac:input>
|
|
99
101
|
|
|
100
102
|
<!-- Name with alpha validation -->
|
|
101
|
-
<odac:
|
|
103
|
+
<odac:input name="full_name" type="text" label="Full Name" placeholder="John Doe">
|
|
102
104
|
<odac:validate rule="required" message="Full name is required"/>
|
|
103
105
|
<odac:validate rule="minlen:2" message="Name must be at least 2 characters"/>
|
|
104
106
|
<odac:validate rule="maxlen:50" message="Name is too long"/>
|
|
105
|
-
</odac:
|
|
107
|
+
</odac:input>
|
|
106
108
|
```
|
|
107
109
|
|
|
108
110
|
### Field Attributes
|
|
@@ -121,9 +123,9 @@ Supports all standard HTML input types:
|
|
|
121
123
|
Add validation rules to fields:
|
|
122
124
|
|
|
123
125
|
```html
|
|
124
|
-
<odac:
|
|
126
|
+
<odac:input name="username" type="text">
|
|
125
127
|
<odac:validate rule="required|minlen:3|maxlen:20" message="Username must be 3-20 characters"/>
|
|
126
|
-
</odac:
|
|
128
|
+
</odac:input>
|
|
127
129
|
```
|
|
128
130
|
|
|
129
131
|
### Available Rules
|
|
@@ -202,59 +204,64 @@ Automatically set field values without user input:
|
|
|
202
204
|
<odac:submit text="Save" loading="Saving..." class="btn btn-primary" id="save-btn"/>
|
|
203
205
|
```
|
|
204
206
|
|
|
205
|
-
## Controller Handler
|
|
207
|
+
## Controller Handler (Server Actions)
|
|
208
|
+
|
|
209
|
+
Handle form submission directly in your controller. The action is defined as `ControllerName.methodName`.
|
|
206
210
|
|
|
207
|
-
|
|
211
|
+
**View:**
|
|
212
|
+
```html
|
|
213
|
+
<odac:form action="Contact.submit">
|
|
214
|
+
...
|
|
215
|
+
</odac:form>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Controller (controller/Contact.js):**
|
|
208
219
|
|
|
209
220
|
```javascript
|
|
210
|
-
module.exports = {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
message: 'Form submitted successfully!',
|
|
225
|
-
redirect: '/thank-you' // Optional redirect
|
|
226
|
-
}
|
|
227
|
-
})
|
|
221
|
+
module.exports = class Contact {
|
|
222
|
+
constructor(Odac) {
|
|
223
|
+
this.Odac = Odac
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async submit(form) {
|
|
227
|
+
// 1. Access validated clean data
|
|
228
|
+
const { email, message } = form.data
|
|
229
|
+
|
|
230
|
+
// 2. Perform your logic
|
|
231
|
+
// await this.Odac.Mail().send(email, message)
|
|
232
|
+
|
|
233
|
+
// 3. Return success easily
|
|
234
|
+
return form.success('Message sent successfully!', '/thank-you')
|
|
228
235
|
}
|
|
229
236
|
}
|
|
230
237
|
```
|
|
231
238
|
|
|
232
239
|
### Error Handling
|
|
233
240
|
|
|
234
|
-
Return
|
|
241
|
+
Return errors using the helper method:
|
|
235
242
|
|
|
236
243
|
```javascript
|
|
237
|
-
module.exports = {
|
|
238
|
-
|
|
239
|
-
|
|
244
|
+
module.exports = class Contact {
|
|
245
|
+
constructor(Odac) {
|
|
246
|
+
this.Odac = Odac
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async submit(form) {
|
|
250
|
+
const { email } = form.data
|
|
240
251
|
|
|
241
|
-
// Custom validation
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
errors: {
|
|
246
|
-
email: 'This email is not allowed'
|
|
247
|
-
}
|
|
248
|
-
})
|
|
252
|
+
// Custom backend validation
|
|
253
|
+
if (email.includes('spam')) {
|
|
254
|
+
// Returns field-specific error
|
|
255
|
+
return form.error('email', 'Spam is not allowed!')
|
|
249
256
|
}
|
|
250
257
|
|
|
251
|
-
return
|
|
252
|
-
result: {success: true, message: 'Success!'}
|
|
253
|
-
})
|
|
258
|
+
return form.success('Success!')
|
|
254
259
|
}
|
|
255
260
|
}
|
|
256
261
|
```
|
|
257
262
|
|
|
263
|
+
**Note:** No route definition is needed for the action! The form system handles the routing securely.
|
|
264
|
+
|
|
258
265
|
## Automatic Database Insert
|
|
259
266
|
|
|
260
267
|
Forms can automatically insert data into database without writing a controller:
|
|
@@ -263,13 +270,13 @@ Forms can automatically insert data into database without writing a controller:
|
|
|
263
270
|
|
|
264
271
|
```html
|
|
265
272
|
<odac:form table="waitlist" redirect="/" success="Thank you for joining!">
|
|
266
|
-
<odac:
|
|
273
|
+
<odac:input name="email" type="email" label="Email">
|
|
267
274
|
<odac:validate rule="required|email|unique" message="Valid email required"/>
|
|
268
|
-
</odac:
|
|
275
|
+
</odac:input>
|
|
269
276
|
|
|
270
|
-
<odac:
|
|
277
|
+
<odac:input name="name" type="text" label="Name">
|
|
271
278
|
<odac:validate rule="required|minlen:2" message="Name required"/>
|
|
272
|
-
</odac:
|
|
279
|
+
</odac:input>
|
|
273
280
|
|
|
274
281
|
<odac:set name="created_at" compute="now"/>
|
|
275
282
|
<odac:set name="ip" compute="ip"/>
|
|
@@ -312,22 +319,22 @@ That's it! No controller needed. The form will:
|
|
|
312
319
|
<div class="contact-page">
|
|
313
320
|
<h1>Contact Us</h1>
|
|
314
321
|
|
|
315
|
-
<odac:form action="
|
|
316
|
-
<odac:
|
|
322
|
+
<odac:form action="Contact.submit" method="POST" class="contact-form">
|
|
323
|
+
<odac:input name="name" type="text" label="Your Name" placeholder="Enter your name">
|
|
317
324
|
<odac:validate rule="required|minlen:3" message="Name must be at least 3 characters"/>
|
|
318
|
-
</odac:
|
|
325
|
+
</odac:input>
|
|
319
326
|
|
|
320
|
-
<odac:
|
|
327
|
+
<odac:input name="email" type="email" label="Email" placeholder="your@email.com">
|
|
321
328
|
<odac:validate rule="required|email" message="Please enter a valid email"/>
|
|
322
|
-
</odac:
|
|
329
|
+
</odac:input>
|
|
323
330
|
|
|
324
|
-
<odac:
|
|
331
|
+
<odac:input name="subject" type="text" label="Subject" placeholder="What is this about?">
|
|
325
332
|
<odac:validate rule="required|minlen:5" message="Subject must be at least 5 characters"/>
|
|
326
|
-
</odac:
|
|
333
|
+
</odac:input>
|
|
327
334
|
|
|
328
|
-
<odac:
|
|
335
|
+
<odac:input name="message" type="textarea" label="Message" placeholder="Your message...">
|
|
329
336
|
<odac:validate rule="required|minlen:10" message="Message must be at least 10 characters"/>
|
|
330
|
-
</odac:
|
|
337
|
+
</odac:input>
|
|
331
338
|
|
|
332
339
|
<odac:submit text="Send Message" loading="Sending..." class="btn btn-primary"/>
|
|
333
340
|
</odac:form>
|
|
@@ -337,29 +344,29 @@ That's it! No controller needed. The form will:
|
|
|
337
344
|
### Controller (controller/contact.js)
|
|
338
345
|
|
|
339
346
|
```javascript
|
|
340
|
-
module.exports = {
|
|
341
|
-
|
|
342
|
-
Odac
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
347
|
+
module.exports = class Contact {
|
|
348
|
+
constructor(Odac) {
|
|
349
|
+
this.Odac = Odac
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async index() {
|
|
353
|
+
this.Odac.View.skeleton('default')
|
|
354
|
+
this.Odac.View.set({content: 'contact'})
|
|
355
|
+
this.Odac.View.print()
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async submit(form) {
|
|
359
|
+
// Get validated data from form helper
|
|
360
|
+
const { name, email, subject, message } = form.data
|
|
349
361
|
|
|
350
362
|
// Save to database
|
|
351
|
-
//
|
|
363
|
+
// Save to database
|
|
364
|
+
// await this.Odac.DB.contacts.insert({ name, email, subject, message })
|
|
352
365
|
|
|
353
366
|
// Send email notification
|
|
354
|
-
// await Odac.Mail().to('admin@example.com').subject('New Contact').send(
|
|
367
|
+
// await this.Odac.Mail().to('admin@example.com').subject('New Contact').send(message)
|
|
355
368
|
|
|
356
|
-
return
|
|
357
|
-
result: {
|
|
358
|
-
success: true,
|
|
359
|
-
message: 'Thank you! We will get back to you soon.',
|
|
360
|
-
redirect: '/'
|
|
361
|
-
}
|
|
362
|
-
})
|
|
369
|
+
return form.success('Thank you! We will get back to you soon.', '/')
|
|
363
370
|
}
|
|
364
371
|
}
|
|
365
372
|
```
|
|
@@ -368,7 +375,7 @@ module.exports = {
|
|
|
368
375
|
|
|
369
376
|
```javascript
|
|
370
377
|
Odac.Route.page('/contact', 'contact')
|
|
371
|
-
|
|
378
|
+
// Note: No route needed for contact.submit action!
|
|
372
379
|
```
|
|
373
380
|
|
|
374
381
|
## Features
|
|
@@ -380,6 +387,7 @@ Odac.Route.post('/contact/submit', 'contact.submit')
|
|
|
380
387
|
- **Loading States** - Automatic button state management
|
|
381
388
|
- **Error Display** - Automatic error message rendering
|
|
382
389
|
- **Success Messages** - Built-in success message handling
|
|
390
|
+
- **Auto-Clear** - Clears inputs on success (disable with `clear="false"`)
|
|
383
391
|
- **Redirect Support** - Optional redirect after successful submission
|
|
384
392
|
|
|
385
393
|
## Security
|
|
@@ -6,9 +6,9 @@ Forms can automatically insert data into your database without writing any contr
|
|
|
6
6
|
|
|
7
7
|
```html
|
|
8
8
|
<odac:form table="waitlist">
|
|
9
|
-
<odac:
|
|
9
|
+
<odac:input name="email" type="email" label="Email">
|
|
10
10
|
<odac:validate rule="required|email|unique"/>
|
|
11
|
-
</odac:
|
|
11
|
+
</odac:input>
|
|
12
12
|
|
|
13
13
|
<odac:submit text="Join"/>
|
|
14
14
|
</odac:form>
|
|
@@ -45,13 +45,13 @@ CREATE TABLE `waitlist` (
|
|
|
45
45
|
<h1>Join Our Waitlist</h1>
|
|
46
46
|
|
|
47
47
|
<odac:form table="waitlist" redirect="/" success="Thank you for joining!">
|
|
48
|
-
<odac:
|
|
48
|
+
<odac:input name="email" type="email" label="Email" placeholder="your@email.com">
|
|
49
49
|
<odac:validate rule="required|email|unique" message="Please enter a valid email"/>
|
|
50
|
-
</odac:
|
|
50
|
+
</odac:input>
|
|
51
51
|
|
|
52
|
-
<odac:
|
|
52
|
+
<odac:input name="name" type="text" label="Name" placeholder="Your name">
|
|
53
53
|
<odac:validate rule="required|minlen:2" message="Name is required"/>
|
|
54
|
-
</odac:
|
|
54
|
+
</odac:input>
|
|
55
55
|
|
|
56
56
|
<odac:set name="created_at" compute="now"/>
|
|
57
57
|
<odac:set name="ip" compute="ip"/>
|
|
@@ -110,9 +110,9 @@ Custom success message to display.
|
|
|
110
110
|
Use `unique` rule to prevent duplicate entries:
|
|
111
111
|
|
|
112
112
|
```html
|
|
113
|
-
<odac:
|
|
113
|
+
<odac:input name="email" type="email">
|
|
114
114
|
<odac:validate rule="required|email|unique" message="This email is already registered"/>
|
|
115
|
-
</odac:
|
|
115
|
+
</odac:input>
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
The system will:
|
|
@@ -163,9 +163,9 @@ Only set if field is empty:
|
|
|
163
163
|
|
|
164
164
|
```html
|
|
165
165
|
<odac:form table="newsletter" success="Thanks for subscribing!">
|
|
166
|
-
<odac:
|
|
166
|
+
<odac:input name="email" type="email">
|
|
167
167
|
<odac:validate rule="required|email|unique"/>
|
|
168
|
-
</odac:
|
|
168
|
+
</odac:input>
|
|
169
169
|
|
|
170
170
|
<odac:set name="subscribed_at" compute="now"/>
|
|
171
171
|
<odac:set name="status" value="active"/>
|
|
@@ -178,13 +178,13 @@ Only set if field is empty:
|
|
|
178
178
|
|
|
179
179
|
```html
|
|
180
180
|
<odac:form table="feedback" redirect="/" success="Thank you for your feedback!">
|
|
181
|
-
<odac:
|
|
181
|
+
<odac:input name="rating" type="number" label="Rating (1-5)">
|
|
182
182
|
<odac:validate rule="required|min:1|max:5"/>
|
|
183
|
-
</odac:
|
|
183
|
+
</odac:input>
|
|
184
184
|
|
|
185
|
-
<odac:
|
|
185
|
+
<odac:input name="comment" type="textarea" label="Comment">
|
|
186
186
|
<odac:validate rule="required|minlen:10"/>
|
|
187
|
-
</odac:
|
|
187
|
+
</odac:input>
|
|
188
188
|
|
|
189
189
|
<odac:set name="created_at" compute="now"/>
|
|
190
190
|
<odac:set name="ip" compute="ip"/>
|
|
@@ -197,17 +197,17 @@ Only set if field is empty:
|
|
|
197
197
|
|
|
198
198
|
```html
|
|
199
199
|
<odac:form table="beta_requests" success="You're on the list!">
|
|
200
|
-
<odac:
|
|
200
|
+
<odac:input name="email" type="email">
|
|
201
201
|
<odac:validate rule="required|email|unique"/>
|
|
202
|
-
</odac:
|
|
202
|
+
</odac:input>
|
|
203
203
|
|
|
204
|
-
<odac:
|
|
204
|
+
<odac:input name="company" type="text">
|
|
205
205
|
<odac:validate rule="required"/>
|
|
206
|
-
</odac:
|
|
206
|
+
</odac:input>
|
|
207
207
|
|
|
208
|
-
<odac:
|
|
208
|
+
<odac:input name="use_case" type="textarea">
|
|
209
209
|
<odac:validate rule="required|minlen:20"/>
|
|
210
|
-
</odac:
|
|
210
|
+
</odac:input>
|
|
211
211
|
|
|
212
212
|
<odac:set name="requested_at" compute="now"/>
|
|
213
213
|
<odac:set name="status" value="pending"/>
|
|
@@ -270,7 +270,7 @@ Odac.Route.post('/contact/submit', async Odac => {
|
|
|
270
270
|
await sendEmail(Odac.formData.email, 'Thank you!')
|
|
271
271
|
|
|
272
272
|
// Manually insert to database if needed
|
|
273
|
-
await Odac.
|
|
273
|
+
await Odac.DB.contacts.insert(Odac.formData)
|
|
274
274
|
|
|
275
275
|
return Odac.return({
|
|
276
276
|
result: {success: true, message: 'Message sent!'}
|
package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md
CHANGED
|
@@ -94,3 +94,20 @@ module.exports = async function (Odac) {
|
|
|
94
94
|
})
|
|
95
95
|
}
|
|
96
96
|
```
|
|
97
|
+
|
|
98
|
+
### Session Data
|
|
99
|
+
|
|
100
|
+
You can store data in the current user's session using `Odac.session()`. This data persists across requests.
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
module.exports = async function (Odac) {
|
|
104
|
+
// Set a session value
|
|
105
|
+
Odac.session('cart_id', 12345)
|
|
106
|
+
|
|
107
|
+
// Get a session value
|
|
108
|
+
const cartId = Odac.session('cart_id')
|
|
109
|
+
|
|
110
|
+
// Remove a session value
|
|
111
|
+
Odac.session('cart_id', null)
|
|
112
|
+
}
|
|
113
|
+
```
|
|
@@ -118,7 +118,7 @@ Create a separate view part for the `<head>` section:
|
|
|
118
118
|
```javascript
|
|
119
119
|
module.exports = async function (Odac) {
|
|
120
120
|
const productId = Odac.Request.get('id')
|
|
121
|
-
const product = await Odac.
|
|
121
|
+
const product = await Odac.DB.table('products')
|
|
122
122
|
.where('id', productId)
|
|
123
123
|
.first()
|
|
124
124
|
|