odac 0.9.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/.editorconfig +21 -0
- package/.github/workflows/auto-pr-description.yml +49 -0
- package/.github/workflows/release.yml +32 -0
- package/.github/workflows/test-coverage.yml +58 -0
- package/.husky/pre-commit +2 -0
- package/.kiro/steering/code-style.md +56 -0
- package/.kiro/steering/product.md +20 -0
- package/.kiro/steering/structure.md +77 -0
- package/.kiro/steering/tech.md +87 -0
- package/.prettierrc +10 -0
- package/.releaserc.js +134 -0
- package/AGENTS.md +84 -0
- package/CHANGELOG.md +181 -0
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +63 -0
- package/LICENSE +661 -0
- package/README.md +57 -0
- package/SECURITY.md +26 -0
- package/bin/candy +10 -0
- package/bin/candypack +10 -0
- package/cli/index.js +3 -0
- package/cli/src/Cli.js +348 -0
- package/cli/src/Connector.js +93 -0
- package/cli/src/Monitor.js +416 -0
- package/core/Candy.js +87 -0
- package/core/Commands.js +239 -0
- package/core/Config.js +1094 -0
- package/core/Lang.js +52 -0
- package/core/Log.js +43 -0
- package/core/Process.js +26 -0
- package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
- package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
- package/docs/backend/01-overview/03-development-server.md +79 -0
- package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
- package/docs/backend/03-config/00-configuration-overview.md +214 -0
- package/docs/backend/03-config/01-database-connection.md +60 -0
- package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
- package/docs/backend/03-config/03-request-timeout.md +11 -0
- package/docs/backend/03-config/04-environment-variables.md +227 -0
- package/docs/backend/03-config/05-early-hints.md +352 -0
- package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
- package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
- package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
- package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
- package/docs/backend/04-routing/05-advanced-routing.md +14 -0
- package/docs/backend/04-routing/06-error-pages.md +101 -0
- package/docs/backend/04-routing/07-cron-jobs.md +149 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
- package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
- package/docs/backend/05-controllers/03-controller-classes.md +93 -0
- package/docs/backend/05-forms/01-custom-forms.md +395 -0
- package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
- package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
- package/docs/backend/07-views/01-the-view-directory.md +73 -0
- package/docs/backend/07-views/02-rendering-a-view.md +179 -0
- package/docs/backend/07-views/03-template-syntax.md +181 -0
- package/docs/backend/07-views/03-variables.md +328 -0
- package/docs/backend/07-views/04-request-data.md +231 -0
- package/docs/backend/07-views/05-conditionals.md +290 -0
- package/docs/backend/07-views/06-loops.md +353 -0
- package/docs/backend/07-views/07-translations.md +358 -0
- package/docs/backend/07-views/08-backend-javascript.md +398 -0
- package/docs/backend/07-views/09-comments.md +297 -0
- package/docs/backend/08-database/01-database-connection.md +99 -0
- package/docs/backend/08-database/02-using-mysql.md +322 -0
- package/docs/backend/09-validation/01-the-validator-service.md +424 -0
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
- package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
- package/docs/backend/10-authentication/03-register.md +134 -0
- package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
- package/docs/backend/10-authentication/05-session-management.md +159 -0
- package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
- package/docs/backend/11-mail/01-the-mail-service.md +42 -0
- package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
- package/docs/backend/13-utilities/01-candy-var.md +504 -0
- package/docs/frontend/01-overview/01-introduction.md +146 -0
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
- package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
- package/docs/frontend/03-forms/01-form-handling.md +420 -0
- package/docs/frontend/04-api-requests/01-get-post.md +443 -0
- package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
- package/docs/index.json +452 -0
- package/docs/server/01-installation/01-quick-install.md +19 -0
- package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
- package/docs/server/02-get-started/01-core-concepts.md +7 -0
- package/docs/server/02-get-started/02-basic-commands.md +57 -0
- package/docs/server/02-get-started/03-cli-reference.md +276 -0
- package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
- package/docs/server/03-service/01-start-a-new-service.md +57 -0
- package/docs/server/03-service/02-delete-a-service.md +48 -0
- package/docs/server/04-web/01-create-a-website.md +36 -0
- package/docs/server/04-web/02-list-websites.md +9 -0
- package/docs/server/04-web/03-delete-a-website.md +29 -0
- package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
- package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
- package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
- package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
- package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
- package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
- package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
- package/docs/server/07-mail/04-change-account-password.md +23 -0
- package/eslint.config.mjs +120 -0
- package/framework/index.js +4 -0
- package/framework/src/Auth.js +309 -0
- package/framework/src/Candy.js +81 -0
- package/framework/src/Config.js +79 -0
- package/framework/src/Env.js +60 -0
- package/framework/src/Lang.js +57 -0
- package/framework/src/Mail.js +83 -0
- package/framework/src/Mysql.js +575 -0
- package/framework/src/Request.js +301 -0
- package/framework/src/Route/Cron.js +128 -0
- package/framework/src/Route/Internal.js +439 -0
- package/framework/src/Route.js +455 -0
- package/framework/src/Server.js +15 -0
- package/framework/src/Stream.js +163 -0
- package/framework/src/Token.js +37 -0
- package/framework/src/Validator.js +271 -0
- package/framework/src/Var.js +211 -0
- package/framework/src/View/EarlyHints.js +190 -0
- package/framework/src/View/Form.js +600 -0
- package/framework/src/View.js +513 -0
- package/framework/web/candy.js +838 -0
- package/jest.config.js +22 -0
- package/locale/de-DE.json +80 -0
- package/locale/en-US.json +79 -0
- package/locale/es-ES.json +80 -0
- package/locale/fr-FR.json +80 -0
- package/locale/pt-BR.json +80 -0
- package/locale/ru-RU.json +80 -0
- package/locale/tr-TR.json +85 -0
- package/locale/zh-CN.json +80 -0
- package/package.json +86 -0
- package/server/index.js +5 -0
- package/server/src/Api.js +88 -0
- package/server/src/DNS.js +940 -0
- package/server/src/Hub.js +535 -0
- package/server/src/Mail.js +571 -0
- package/server/src/SSL.js +180 -0
- package/server/src/Server.js +27 -0
- package/server/src/Service.js +248 -0
- package/server/src/Subdomain.js +64 -0
- package/server/src/Web/Firewall.js +170 -0
- package/server/src/Web/Proxy.js +134 -0
- package/server/src/Web.js +451 -0
- package/server/src/mail/imap.js +1091 -0
- package/server/src/mail/server.js +32 -0
- package/server/src/mail/smtp.js +786 -0
- package/test/cli/Cli.test.js +36 -0
- package/test/core/Candy.test.js +234 -0
- package/test/core/Commands.test.js +538 -0
- package/test/core/Config.test.js +1435 -0
- package/test/core/Lang.test.js +250 -0
- package/test/core/Process.test.js +156 -0
- package/test/framework/Route.test.js +239 -0
- package/test/framework/View/EarlyHints.test.js +282 -0
- package/test/scripts/check-coverage.js +132 -0
- package/test/server/Api.test.js +647 -0
- package/test/server/Client.test.js +338 -0
- package/test/server/DNS.test.js +2050 -0
- package/test/server/DNS.test.js.bak +2084 -0
- package/test/server/Log.test.js +73 -0
- package/test/server/Mail.account.test_.js +460 -0
- package/test/server/Mail.init.test_.js +411 -0
- package/test/server/Mail.test_.js +1340 -0
- package/test/server/SSL.test_.js +1491 -0
- package/test/server/Server.test.js +765 -0
- package/test/server/Service.test_.js +1127 -0
- package/test/server/Subdomain.test.js +440 -0
- package/test/server/Web/Firewall.test.js +175 -0
- package/test/server/Web.test_.js +1562 -0
- package/test/server/__mocks__/acme-client.js +17 -0
- package/test/server/__mocks__/bcrypt.js +50 -0
- package/test/server/__mocks__/child_process.js +389 -0
- package/test/server/__mocks__/crypto.js +432 -0
- package/test/server/__mocks__/fs.js +450 -0
- package/test/server/__mocks__/globalCandy.js +227 -0
- package/test/server/__mocks__/http-proxy.js +105 -0
- package/test/server/__mocks__/http.js +575 -0
- package/test/server/__mocks__/https.js +272 -0
- package/test/server/__mocks__/index.js +249 -0
- package/test/server/__mocks__/mail/server.js +100 -0
- package/test/server/__mocks__/mail/smtp.js +31 -0
- package/test/server/__mocks__/mailparser.js +81 -0
- package/test/server/__mocks__/net.js +369 -0
- package/test/server/__mocks__/node-forge.js +328 -0
- package/test/server/__mocks__/os.js +320 -0
- package/test/server/__mocks__/path.js +291 -0
- package/test/server/__mocks__/selfsigned.js +8 -0
- package/test/server/__mocks__/server/src/mail/server.js +100 -0
- package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
- package/test/server/__mocks__/smtp-server.js +106 -0
- package/test/server/__mocks__/sqlite3.js +394 -0
- package/test/server/__mocks__/testFactories.js +299 -0
- package/test/server/__mocks__/testHelpers.js +363 -0
- package/test/server/__mocks__/tls.js +229 -0
- package/watchdog/index.js +3 -0
- package/watchdog/src/Watchdog.js +156 -0
- package/web/config.json +5 -0
- package/web/controller/page/about.js +27 -0
- package/web/controller/page/index.js +34 -0
- package/web/package.json +18 -0
- package/web/public/assets/css/style.css +1835 -0
- package/web/public/assets/js/app.js +96 -0
- package/web/route/www.js +19 -0
- package/web/skeleton/main.html +22 -0
- package/web/view/content/about.html +65 -0
- package/web/view/content/home.html +205 -0
- package/web/view/footer/main.html +11 -0
- package/web/view/head/main.html +5 -0
- package/web/view/header/main.html +14 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
## 🔧 Candy.Var - String Manipulation & Validation
|
|
2
|
+
|
|
3
|
+
`Candy.Var` is a powerful utility class for string manipulation, validation, encryption, and formatting. It provides a chainable, fluent interface for common string operations.
|
|
4
|
+
|
|
5
|
+
### Basic Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// Create a Var instance
|
|
9
|
+
const result = Candy.Var('hello world').slug()
|
|
10
|
+
// Returns: 'hello-world'
|
|
11
|
+
|
|
12
|
+
// Chain multiple operations
|
|
13
|
+
const email = Candy.Var(' USER@EXAMPLE.COM ').trim().toLowerCase()
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### String Validation
|
|
17
|
+
|
|
18
|
+
Check if a string matches specific patterns:
|
|
19
|
+
|
|
20
|
+
#### is() - Single Validation
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
// Email validation
|
|
24
|
+
Candy.Var('user@example.com').is('email') // true
|
|
25
|
+
Candy.Var('invalid-email').is('email') // false
|
|
26
|
+
|
|
27
|
+
// Numeric validation
|
|
28
|
+
Candy.Var('12345').is('numeric') // true
|
|
29
|
+
Candy.Var('abc123').is('numeric') // false
|
|
30
|
+
|
|
31
|
+
// Multiple conditions (AND logic)
|
|
32
|
+
Candy.Var('abc123').is('alphanumeric') // true
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
#### isAny() - Multiple Validation (OR logic)
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
// Check if value matches ANY of the conditions
|
|
39
|
+
Candy.Var('user@example.com').isAny('email', 'domain') // true
|
|
40
|
+
Candy.Var('example.com').isAny('email', 'domain') // true
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### Available Validation Types
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
'alpha' // Only letters (A-Z, a-z)
|
|
47
|
+
'alphaspace' // Letters and spaces
|
|
48
|
+
'alphanumeric' // Letters and numbers
|
|
49
|
+
'alphanumericspace' // Letters, numbers, and spaces
|
|
50
|
+
'bcrypt' // BCrypt hash format
|
|
51
|
+
'date' // Valid date string
|
|
52
|
+
'domain' // Valid domain name (example.com)
|
|
53
|
+
'email' // Valid email address
|
|
54
|
+
'float' // Floating point number
|
|
55
|
+
'host' // IP address
|
|
56
|
+
'ip' // IP address
|
|
57
|
+
'json' // Valid JSON string
|
|
58
|
+
'mac' // MAC address
|
|
59
|
+
'md5' // MD5 hash
|
|
60
|
+
'numeric' // Numbers only
|
|
61
|
+
'url' // Valid URL
|
|
62
|
+
'emoji' // Contains emoji
|
|
63
|
+
'xss' // XSS-safe (no HTML tags)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### Practical Examples
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
// Controller validation
|
|
70
|
+
module.exports = async function(Candy) {
|
|
71
|
+
const email = Candy.Request.post('email')
|
|
72
|
+
|
|
73
|
+
if (!Candy.Var(email).is('email')) {
|
|
74
|
+
return Candy.return({
|
|
75
|
+
success: false,
|
|
76
|
+
message: 'Invalid email address'
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Continue with valid email
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### String Checking
|
|
85
|
+
|
|
86
|
+
#### contains() - Check if string contains values
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
// Single value
|
|
90
|
+
Candy.Var('hello world').contains('world') // true
|
|
91
|
+
Candy.Var('hello world').contains('foo') // false
|
|
92
|
+
|
|
93
|
+
// Multiple values (AND logic - must contain all)
|
|
94
|
+
Candy.Var('hello world').contains('hello', 'world') // true
|
|
95
|
+
Candy.Var('hello world').contains('hello', 'foo') // false
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### containsAny() - Check if string contains any value
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
// Check if contains ANY of the values (OR logic)
|
|
102
|
+
Candy.Var('hello world').containsAny('foo', 'world') // true
|
|
103
|
+
Candy.Var('hello world').containsAny('foo', 'bar') // false
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### isBegin() - Check if string starts with value
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
Candy.Var('hello world').isBegin('hello') // true
|
|
110
|
+
Candy.Var('hello world').isBegin('world') // false
|
|
111
|
+
|
|
112
|
+
// Multiple options
|
|
113
|
+
Candy.Var('https://example.com').isBegin('http://', 'https://') // true
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### isEnd() - Check if string ends with value
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
Candy.Var('hello world').isEnd('world') // true
|
|
120
|
+
Candy.Var('hello world').isEnd('hello') // false
|
|
121
|
+
|
|
122
|
+
// Multiple options
|
|
123
|
+
Candy.Var('image.jpg').isEnd('.jpg', '.png', '.gif') // true
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### String Manipulation
|
|
127
|
+
|
|
128
|
+
#### replace() - Replace text
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
// Simple replacement
|
|
132
|
+
Candy.Var('hello world').replace('world', 'universe')
|
|
133
|
+
// Returns: 'hello universe'
|
|
134
|
+
|
|
135
|
+
// Multiple replacements with object
|
|
136
|
+
Candy.Var('Hello {{name}}, welcome to {{site}}').replace({
|
|
137
|
+
'{{name}}': 'John',
|
|
138
|
+
'{{site}}': 'CandyPack'
|
|
139
|
+
})
|
|
140
|
+
// Returns: 'Hello John, welcome to CandyPack'
|
|
141
|
+
|
|
142
|
+
// Works with arrays/objects recursively
|
|
143
|
+
const data = {
|
|
144
|
+
title: 'Welcome {{name}}',
|
|
145
|
+
message: 'Hello {{name}}'
|
|
146
|
+
}
|
|
147
|
+
Candy.Var(data).replace({'{{name}}': 'John'})
|
|
148
|
+
// Returns: { title: 'Welcome John', message: 'Hello John' }
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### clear() - Remove specific strings
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
Candy.Var('hello-world-test').clear('-')
|
|
155
|
+
// Returns: 'helloworldtest'
|
|
156
|
+
|
|
157
|
+
// Remove multiple strings
|
|
158
|
+
Candy.Var('a1b2c3').clear('1', '2', '3')
|
|
159
|
+
// Returns: 'abc'
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### slug() - Create URL-friendly slug
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
Candy.Var('Hello World!').slug()
|
|
166
|
+
// Returns: 'hello-world'
|
|
167
|
+
|
|
168
|
+
Candy.Var('Product Name 2024').slug()
|
|
169
|
+
// Returns: 'product-name-2024'
|
|
170
|
+
|
|
171
|
+
// Custom separator
|
|
172
|
+
Candy.Var('Hello World').slug('_')
|
|
173
|
+
// Returns: 'hello_world'
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### format() - Format string with pattern
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
// ? = single character, * = rest of string
|
|
180
|
+
Candy.Var('1234567890').format('(???) ???-????')
|
|
181
|
+
// Returns: '(123) 456-7890'
|
|
182
|
+
|
|
183
|
+
Candy.Var('TR1234567890').format('?? *')
|
|
184
|
+
// Returns: 'TR 1234567890'
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### html() - Escape HTML
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
Candy.Var('<script>alert("xss")</script>').html()
|
|
191
|
+
// Returns: '<script>alert("xss")</script>'
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Encryption & Hashing
|
|
195
|
+
|
|
196
|
+
#### hash() - BCrypt password hashing
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
// Hash a password
|
|
200
|
+
const hashedPassword = Candy.Var('mypassword').hash()
|
|
201
|
+
// Returns: '$2b$10$...' (BCrypt hash)
|
|
202
|
+
|
|
203
|
+
// Custom salt rounds
|
|
204
|
+
const hashedPassword = Candy.Var('mypassword').hash(12)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### hashCheck() - Verify BCrypt hash
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
const hashedPassword = '$2b$10$...'
|
|
211
|
+
const isValid = Candy.Var(hashedPassword).hashCheck('mypassword')
|
|
212
|
+
// Returns: true or false
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### md5() - MD5 hash
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
Candy.Var('hello').md5()
|
|
219
|
+
// Returns: '5d41402abc4b2a76b9719d911017c592'
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### encrypt() - AES-256 encryption
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
// Uses key from Candy.Config.encrypt.key
|
|
226
|
+
const encrypted = Candy.Var('secret data').encrypt()
|
|
227
|
+
|
|
228
|
+
// Custom encryption key
|
|
229
|
+
const encrypted = Candy.Var('secret data').encrypt('my-32-character-encryption-key')
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### decrypt() - AES-256 decryption
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
// Uses key from Candy.Config.encrypt.key
|
|
236
|
+
const decrypted = Candy.Var(encryptedData).decrypt()
|
|
237
|
+
|
|
238
|
+
// Custom decryption key
|
|
239
|
+
const decrypted = Candy.Var(encryptedData).decrypt('my-32-character-encryption-key')
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Date Formatting
|
|
243
|
+
|
|
244
|
+
#### date() - Format date strings
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
const timestamp = '2024-03-15 14:30:45'
|
|
248
|
+
|
|
249
|
+
Candy.Var(timestamp).date('Y-m-d')
|
|
250
|
+
// Returns: '2024-03-15'
|
|
251
|
+
|
|
252
|
+
Candy.Var(timestamp).date('d/m/Y')
|
|
253
|
+
// Returns: '15/03/2024'
|
|
254
|
+
|
|
255
|
+
Candy.Var(timestamp).date('H:i:s')
|
|
256
|
+
// Returns: '14:30:45'
|
|
257
|
+
|
|
258
|
+
Candy.Var(timestamp).date('Y-m-d H:i')
|
|
259
|
+
// Returns: '2024-03-15 14:30'
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Format tokens:**
|
|
263
|
+
- `Y` - 4-digit year (2024)
|
|
264
|
+
- `y` - 2-digit year (24)
|
|
265
|
+
- `m` - Month with leading zero (01-12)
|
|
266
|
+
- `d` - Day with leading zero (01-31)
|
|
267
|
+
- `H` - Hour with leading zero (00-23)
|
|
268
|
+
- `i` - Minute with leading zero (00-59)
|
|
269
|
+
- `s` - Second with leading zero (00-59)
|
|
270
|
+
|
|
271
|
+
### File Operations
|
|
272
|
+
|
|
273
|
+
#### save() - Save string to file
|
|
274
|
+
|
|
275
|
+
```javascript
|
|
276
|
+
// Save content to file
|
|
277
|
+
Candy.Var('Hello World').save('/path/to/file.txt')
|
|
278
|
+
|
|
279
|
+
// Automatically creates directories if needed
|
|
280
|
+
Candy.Var(jsonData).save('/path/to/nested/dir/data.json')
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Practical Examples
|
|
284
|
+
|
|
285
|
+
#### User Registration with Validation
|
|
286
|
+
|
|
287
|
+
```javascript
|
|
288
|
+
module.exports = async function(Candy) {
|
|
289
|
+
const email = Candy.Request.post('email')
|
|
290
|
+
const password = Candy.Request.post('password')
|
|
291
|
+
const username = Candy.Request.post('username')
|
|
292
|
+
|
|
293
|
+
// Validate email
|
|
294
|
+
if (!Candy.Var(email).is('email')) {
|
|
295
|
+
return Candy.return({
|
|
296
|
+
success: false,
|
|
297
|
+
message: 'Invalid email address'
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Validate username (alphanumeric only)
|
|
302
|
+
if (!Candy.Var(username).is('alphanumeric')) {
|
|
303
|
+
return Candy.return({
|
|
304
|
+
success: false,
|
|
305
|
+
message: 'Username must be alphanumeric'
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Hash password
|
|
310
|
+
const hashedPassword = Candy.Var(password).hash()
|
|
311
|
+
|
|
312
|
+
// Create slug for profile URL
|
|
313
|
+
const profileSlug = Candy.Var(username).slug()
|
|
314
|
+
|
|
315
|
+
// Save user
|
|
316
|
+
await Candy.Mysql.table('users').insert({
|
|
317
|
+
email: email,
|
|
318
|
+
username: username,
|
|
319
|
+
password: hashedPassword,
|
|
320
|
+
slug: profileSlug
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
return Candy.return({success: true})
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
#### Login with Password Verification
|
|
328
|
+
|
|
329
|
+
```javascript
|
|
330
|
+
module.exports = async function(Candy) {
|
|
331
|
+
const email = Candy.Request.post('email')
|
|
332
|
+
const password = Candy.Request.post('password')
|
|
333
|
+
|
|
334
|
+
// Find user
|
|
335
|
+
const user = await Candy.Mysql.table('users')
|
|
336
|
+
.where('email', email)
|
|
337
|
+
.first()
|
|
338
|
+
|
|
339
|
+
if (!user) {
|
|
340
|
+
return Candy.return({
|
|
341
|
+
success: false,
|
|
342
|
+
message: 'User not found'
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Verify password
|
|
347
|
+
const isValid = Candy.Var(user.password).hashCheck(password)
|
|
348
|
+
|
|
349
|
+
if (!isValid) {
|
|
350
|
+
return Candy.return({
|
|
351
|
+
success: false,
|
|
352
|
+
message: 'Invalid password'
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Login successful
|
|
357
|
+
Candy.Auth.login(user.id)
|
|
358
|
+
return Candy.return({success: true})
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### URL Slug Generation
|
|
363
|
+
|
|
364
|
+
```javascript
|
|
365
|
+
module.exports = async function(Candy) {
|
|
366
|
+
const title = Candy.Request.post('title')
|
|
367
|
+
|
|
368
|
+
// Create URL-friendly slug
|
|
369
|
+
const slug = Candy.Var(title).slug()
|
|
370
|
+
|
|
371
|
+
// Check if slug exists
|
|
372
|
+
const exists = await Candy.Mysql.table('posts')
|
|
373
|
+
.where('slug', slug)
|
|
374
|
+
.first()
|
|
375
|
+
|
|
376
|
+
if (exists) {
|
|
377
|
+
// Add timestamp to make unique
|
|
378
|
+
const uniqueSlug = `${slug}-${Date.now()}`
|
|
379
|
+
await Candy.Mysql.table('posts').insert({
|
|
380
|
+
title: title,
|
|
381
|
+
slug: uniqueSlug
|
|
382
|
+
})
|
|
383
|
+
} else {
|
|
384
|
+
await Candy.Mysql.table('posts').insert({
|
|
385
|
+
title: title,
|
|
386
|
+
slug: slug
|
|
387
|
+
})
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return Candy.return({success: true})
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
#### Template Variable Replacement
|
|
395
|
+
|
|
396
|
+
```javascript
|
|
397
|
+
module.exports = async function(Candy) {
|
|
398
|
+
const user = await Candy.Auth.user()
|
|
399
|
+
|
|
400
|
+
// Email template
|
|
401
|
+
const template = `
|
|
402
|
+
Hello {{name}},
|
|
403
|
+
|
|
404
|
+
Your account {{email}} has been verified.
|
|
405
|
+
You can now access your dashboard at {{url}}.
|
|
406
|
+
|
|
407
|
+
Thanks,
|
|
408
|
+
{{site}}
|
|
409
|
+
`
|
|
410
|
+
|
|
411
|
+
// Replace variables
|
|
412
|
+
const emailContent = Candy.Var(template).replace({
|
|
413
|
+
'{{name}}': user.name,
|
|
414
|
+
'{{email}}': user.email,
|
|
415
|
+
'{{url}}': 'https://example.com/dashboard',
|
|
416
|
+
'{{site}}': 'CandyPack'
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
// Send email
|
|
420
|
+
await Candy.Mail.send({
|
|
421
|
+
to: user.email,
|
|
422
|
+
subject: 'Account Verified',
|
|
423
|
+
body: emailContent
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
return Candy.return({success: true})
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
#### Phone Number Formatting
|
|
431
|
+
|
|
432
|
+
```javascript
|
|
433
|
+
module.exports = async function(Candy) {
|
|
434
|
+
const phone = Candy.Request.post('phone')
|
|
435
|
+
|
|
436
|
+
// Remove all non-numeric characters
|
|
437
|
+
const cleanPhone = Candy.Var(phone).clear('-', ' ', '(', ')', '+')
|
|
438
|
+
|
|
439
|
+
// Validate it's numeric
|
|
440
|
+
if (!Candy.Var(cleanPhone).is('numeric')) {
|
|
441
|
+
return Candy.return({
|
|
442
|
+
success: false,
|
|
443
|
+
message: 'Invalid phone number'
|
|
444
|
+
})
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Format for display
|
|
448
|
+
const formattedPhone = Candy.Var(cleanPhone).format('(???) ???-????')
|
|
449
|
+
|
|
450
|
+
return Candy.return({
|
|
451
|
+
success: true,
|
|
452
|
+
phone: formattedPhone
|
|
453
|
+
})
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
#### Data Encryption for Storage
|
|
458
|
+
|
|
459
|
+
```javascript
|
|
460
|
+
module.exports = async function(Candy) {
|
|
461
|
+
const creditCard = Candy.Request.post('credit_card')
|
|
462
|
+
|
|
463
|
+
// Encrypt sensitive data
|
|
464
|
+
const encryptedCard = Candy.Var(creditCard).encrypt()
|
|
465
|
+
|
|
466
|
+
// Save encrypted data
|
|
467
|
+
await Candy.Mysql.table('payments').insert({
|
|
468
|
+
user_id: Candy.Auth.id(),
|
|
469
|
+
card: encryptedCard
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
return Candy.return({success: true})
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Later, to retrieve and decrypt
|
|
476
|
+
module.exports = async function(Candy) {
|
|
477
|
+
const payment = await Candy.Mysql.table('payments')
|
|
478
|
+
.where('user_id', Candy.Auth.id())
|
|
479
|
+
.first()
|
|
480
|
+
|
|
481
|
+
// Decrypt data
|
|
482
|
+
const creditCard = Candy.Var(payment.card).decrypt()
|
|
483
|
+
|
|
484
|
+
return Candy.return({
|
|
485
|
+
card: creditCard
|
|
486
|
+
})
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Best Practices
|
|
491
|
+
|
|
492
|
+
1. **Always validate user input** before processing
|
|
493
|
+
2. **Use hash() for passwords**, never store plain text
|
|
494
|
+
3. **Use encrypt() for sensitive data** like credit cards, SSNs
|
|
495
|
+
4. **Create slugs for URLs** to make them SEO-friendly
|
|
496
|
+
5. **Sanitize HTML** with html() to prevent XSS attacks
|
|
497
|
+
6. **Use isBegin/isEnd** for protocol or file extension checks
|
|
498
|
+
|
|
499
|
+
### Notes
|
|
500
|
+
|
|
501
|
+
- `Candy.Var()` returns the processed string value, not a Var instance (except for chaining)
|
|
502
|
+
- Encryption uses AES-256-CBC with a fixed IV
|
|
503
|
+
- BCrypt hashing is one-way and cannot be decrypted
|
|
504
|
+
- Date formatting works with any valid JavaScript date string
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Frontend Javascript Framework: candy.js
|
|
2
|
+
|
|
3
|
+
`candy.js` is a lightweight frontend JavaScript framework designed to simplify interactions with the backend, handle forms, and manage page-specific logic within the CandyPack ecosystem. It provides a set of tools for event handling, AJAX requests, and more, all accessible through the global `Candy` object.
|
|
4
|
+
|
|
5
|
+
## The Global `Candy` Object
|
|
6
|
+
|
|
7
|
+
After including `candy.js` in your page, you will have access to a global `Candy` object. This object is the main entry point for all the features of the framework.
|
|
8
|
+
|
|
9
|
+
## Core Concepts
|
|
10
|
+
|
|
11
|
+
### Actions
|
|
12
|
+
|
|
13
|
+
Actions are the fundamental building block of `candy.js`. An action is a collection of event handlers and lifecycle callbacks that define the behavior of a page or a component.
|
|
14
|
+
|
|
15
|
+
### Pages
|
|
16
|
+
|
|
17
|
+
`candy.js` has a concept of "pages", which allows you to scope your JavaScript to a specific page. The current page identifier is determined by the backend based on:
|
|
18
|
+
- **Controller name** when using controller files (e.g., `'user'` for `controller/page/user.js`)
|
|
19
|
+
- **View name** when using view objects (e.g., `'dashboard'` from `{content: 'dashboard'}`)
|
|
20
|
+
- This identifier is accessible via `Candy.page()` and stored in `data-candy-page` attribute on the `<html>` element.
|
|
21
|
+
|
|
22
|
+
### Lifecycle Events
|
|
23
|
+
|
|
24
|
+
`candy.js` provides several lifecycle events that you can hook into:
|
|
25
|
+
- `start`: Fired once when the script is initialized.
|
|
26
|
+
- `load`: Fired on every page load, after the DOM is ready.
|
|
27
|
+
- `page`: Fired on a specific page, after the DOM is ready.
|
|
28
|
+
- `interval`: Fired repeatedly at a specified interval.
|
|
29
|
+
|
|
30
|
+
## Event Handling with `Candy.action()`
|
|
31
|
+
|
|
32
|
+
The `Candy.action()` method is the most important method in the framework. It allows you to register event handlers and lifecycle callbacks.
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
Candy.action({
|
|
36
|
+
// Fired once on DOMContentLoaded
|
|
37
|
+
start: function() {
|
|
38
|
+
console.log('Candy.js started!');
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// Fired on every page load
|
|
42
|
+
load: function() {
|
|
43
|
+
console.log('Page loaded!');
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Fired only on the 'home' page
|
|
47
|
+
page: {
|
|
48
|
+
home: function() {
|
|
49
|
+
console.log('Welcome to the home page!');
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// Fired every 2 seconds on the 'dashboard' page
|
|
54
|
+
interval: {
|
|
55
|
+
myInterval: {
|
|
56
|
+
interval: 2000,
|
|
57
|
+
page: 'dashboard',
|
|
58
|
+
function: function() {
|
|
59
|
+
console.log('Dashboard is refreshing...');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
// Event handlers
|
|
65
|
+
click: {
|
|
66
|
+
'#my-button': function() {
|
|
67
|
+
alert('Button clicked!');
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// You can also define functions and reference them
|
|
72
|
+
fn: {
|
|
73
|
+
myFunction: function() {
|
|
74
|
+
alert('This is my function!');
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
// And then use them in your event handlers
|
|
79
|
+
mouseover: {
|
|
80
|
+
'#my-element': 'fn.myFunction'
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Working with Forms using `Candy.form()`
|
|
86
|
+
|
|
87
|
+
`Candy.form()` simplifies AJAX form submissions. It handles serialization, validation feedback, success messages, and file uploads automatically.
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
// Basic usage
|
|
91
|
+
Candy.form('#my-form', function(data) {
|
|
92
|
+
// This callback is executed on success
|
|
93
|
+
console.log('Form submitted successfully!', data);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// With options
|
|
97
|
+
Candy.form({
|
|
98
|
+
form: '#my-form',
|
|
99
|
+
messages: ['success', 'error'], // Show both success and error messages
|
|
100
|
+
loading: function(percent) {
|
|
101
|
+
console.log('Upload progress:', percent + '%');
|
|
102
|
+
}
|
|
103
|
+
}, function(data) {
|
|
104
|
+
// Success callback
|
|
105
|
+
if (data.result.success) {
|
|
106
|
+
window.location.href = '/thank-you';
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
To display validation errors, you can add elements with the `candy-form-error` attribute to your form. The value of the attribute should be the `name` of the input field.
|
|
111
|
+
|
|
112
|
+
```html
|
|
113
|
+
<input type="text" name="email">
|
|
114
|
+
<span candy-form-error="email"></span>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Making AJAX requests with `Candy.get()`
|
|
118
|
+
|
|
119
|
+
For simple GET requests, you can use the `Candy.get()` method.
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
Candy.get('/api/users', function(data) {
|
|
123
|
+
console.log('Users:', data);
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Managing CSRF tokens with `Candy.token()`
|
|
128
|
+
|
|
129
|
+
`candy.js` automatically manages CSRF tokens for you. The `Candy.token()` method will return a valid token for your requests. The `Candy.form()` and `Candy.get()` methods use this automatically, so you usually don't need to call it yourself.
|
|
130
|
+
|
|
131
|
+
## Other Utility Functions
|
|
132
|
+
|
|
133
|
+
- **`Candy.client()`**: Returns a unique client identifier from a cookie.
|
|
134
|
+
- **`Candy.data()`**: Returns data from the `candy_data` cookie, which is set by the backend. This data includes the current page and the initial CSRF token.
|
|
135
|
+
- **`Candy.page()`**: Returns the identifier of the current page. This is the controller name (e.g., `'user'`) or view name (e.g., `'dashboard'`) set by the backend. Use this to conditionally run code for specific pages.
|
|
136
|
+
- **`Candy.storage()`**: A wrapper for `localStorage`.
|
|
137
|
+
```javascript
|
|
138
|
+
// Set a value
|
|
139
|
+
Candy.storage('my-key', 'my-value');
|
|
140
|
+
|
|
141
|
+
// Get a value
|
|
142
|
+
let value = Candy.storage('my-key');
|
|
143
|
+
|
|
144
|
+
// Remove a value
|
|
145
|
+
Candy.storage('my-key', null);
|
|
146
|
+
```
|