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,99 @@
|
|
|
1
|
+
## 🔌 Database Connection
|
|
2
|
+
|
|
3
|
+
CandyPack automatically connects to your MySQL database when you provide the configuration.
|
|
4
|
+
|
|
5
|
+
### Configuration
|
|
6
|
+
|
|
7
|
+
Add your database credentials to `config.json`:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"database": {
|
|
12
|
+
"host": "localhost",
|
|
13
|
+
"user": "your_username",
|
|
14
|
+
"password": "your_password",
|
|
15
|
+
"database": "your_database_name"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Multiple Databases
|
|
21
|
+
|
|
22
|
+
You can configure multiple database connections:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"database": {
|
|
27
|
+
"default": {
|
|
28
|
+
"host": "localhost",
|
|
29
|
+
"user": "user1",
|
|
30
|
+
"password": "pass1",
|
|
31
|
+
"database": "main_db"
|
|
32
|
+
},
|
|
33
|
+
"analytics": {
|
|
34
|
+
"host": "analytics.example.com",
|
|
35
|
+
"user": "user2",
|
|
36
|
+
"password": "pass2",
|
|
37
|
+
"database": "analytics_db"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Access different databases:
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
// Default database
|
|
47
|
+
const users = await Candy.Mysql.table('users').get()
|
|
48
|
+
|
|
49
|
+
// Specific database
|
|
50
|
+
const stats = await Candy.Mysql.database('analytics').table('stats').get()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Environment Variables
|
|
54
|
+
|
|
55
|
+
For security, use environment variables for sensitive data:
|
|
56
|
+
|
|
57
|
+
**.env file:**
|
|
58
|
+
```
|
|
59
|
+
DB_HOST=localhost
|
|
60
|
+
DB_USER=myuser
|
|
61
|
+
DB_PASSWORD=mypassword
|
|
62
|
+
DB_NAME=mydatabase
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**config.json:**
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"database": {
|
|
69
|
+
"host": "${DB_HOST}",
|
|
70
|
+
"user": "${DB_USER}",
|
|
71
|
+
"password": "${DB_PASSWORD}",
|
|
72
|
+
"database": "${DB_NAME}"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Connection Options
|
|
78
|
+
|
|
79
|
+
Available configuration options:
|
|
80
|
+
|
|
81
|
+
- `host` - Database server hostname (default: `localhost`)
|
|
82
|
+
- `user` - Database username
|
|
83
|
+
- `password` - Database password
|
|
84
|
+
- `database` - Database name
|
|
85
|
+
- `type` - Database type (currently only `mysql` is supported)
|
|
86
|
+
|
|
87
|
+
### Automatic Connection
|
|
88
|
+
|
|
89
|
+
The connection is established automatically when your application starts. You don't need to write any connection code - just use `Candy.Mysql` in your controllers.
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
module.exports = async function (Candy) {
|
|
93
|
+
// Connection is already established
|
|
94
|
+
const users = await Candy.Mysql.table('users').get()
|
|
95
|
+
|
|
96
|
+
Candy.set('users', users)
|
|
97
|
+
Candy.View.set({ skeleton: 'main', content: 'users' })
|
|
98
|
+
}
|
|
99
|
+
```
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
## 🗄️ Using MySQL
|
|
2
|
+
|
|
3
|
+
CandyPack provides a powerful query builder for safe and easy database operations.
|
|
4
|
+
|
|
5
|
+
### Selecting Data
|
|
6
|
+
|
|
7
|
+
#### Get All Records
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const users = await Candy.Mysql.table('users').get()
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
#### Get First Record
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
const user = await Candy.Mysql.table('users').where('id', 1).first()
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
#### Select Specific Columns
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
const users = await Candy.Mysql.table('users')
|
|
23
|
+
.select('id', 'name', 'email')
|
|
24
|
+
.get()
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Where Conditions
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
// Single condition
|
|
31
|
+
const users = await Candy.Mysql.table('users')
|
|
32
|
+
.where('status', 'active')
|
|
33
|
+
.get()
|
|
34
|
+
|
|
35
|
+
// Multiple conditions (AND)
|
|
36
|
+
const users = await Candy.Mysql.table('users')
|
|
37
|
+
.where('status', 'active')
|
|
38
|
+
.where('verified', 1)
|
|
39
|
+
.get()
|
|
40
|
+
|
|
41
|
+
// OR condition
|
|
42
|
+
const users = await Candy.Mysql.table('users')
|
|
43
|
+
.where('role', 'admin')
|
|
44
|
+
.orWhere('role', 'moderator')
|
|
45
|
+
.get()
|
|
46
|
+
|
|
47
|
+
// Comparison operators
|
|
48
|
+
const products = await Candy.Mysql.table('products')
|
|
49
|
+
.where('price', '>', 100)
|
|
50
|
+
.where('stock', '<=', 10)
|
|
51
|
+
.get()
|
|
52
|
+
|
|
53
|
+
// LIKE
|
|
54
|
+
const users = await Candy.Mysql.table('users')
|
|
55
|
+
.where('name', 'LIKE', '%John%')
|
|
56
|
+
.get()
|
|
57
|
+
|
|
58
|
+
// IN
|
|
59
|
+
const users = await Candy.Mysql.table('users')
|
|
60
|
+
.where('role', 'IN', ['admin', 'moderator'])
|
|
61
|
+
.get()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Complex Where Conditions
|
|
65
|
+
|
|
66
|
+
For complex queries with nested AND/OR conditions, use arrays:
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
// SQL: WHERE (status = 'active' AND verified = 1) OR (role = 'admin')
|
|
70
|
+
const users = await Candy.Mysql.table('users')
|
|
71
|
+
.where([
|
|
72
|
+
['status', 'active'],
|
|
73
|
+
['verified', 1]
|
|
74
|
+
])
|
|
75
|
+
.orWhere(['role', 'admin'])
|
|
76
|
+
.get()
|
|
77
|
+
|
|
78
|
+
// SQL: WHERE status = 'active' AND (role = 'admin' OR role = 'moderator')
|
|
79
|
+
const users = await Candy.Mysql.table('users')
|
|
80
|
+
.where('status', 'active')
|
|
81
|
+
.where([
|
|
82
|
+
['role', 'admin'],
|
|
83
|
+
'OR',
|
|
84
|
+
['role', 'moderator']
|
|
85
|
+
])
|
|
86
|
+
.get()
|
|
87
|
+
|
|
88
|
+
// Complex nested conditions
|
|
89
|
+
// SQL: WHERE (status = 'active' AND (role = 'admin' OR role = 'moderator'))
|
|
90
|
+
// AND (verified = 1 OR email_verified = 1)
|
|
91
|
+
const users = await Candy.Mysql.table('users')
|
|
92
|
+
.where([
|
|
93
|
+
['status', 'active'],
|
|
94
|
+
[
|
|
95
|
+
['role', 'admin'],
|
|
96
|
+
'OR',
|
|
97
|
+
['role', 'moderator']
|
|
98
|
+
]
|
|
99
|
+
])
|
|
100
|
+
.where([
|
|
101
|
+
['verified', 1],
|
|
102
|
+
'OR',
|
|
103
|
+
['email_verified', 1]
|
|
104
|
+
])
|
|
105
|
+
.get()
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Ordering and Limiting
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
// Order by
|
|
112
|
+
const users = await Candy.Mysql.table('users')
|
|
113
|
+
.order('created_at', 'desc')
|
|
114
|
+
.get()
|
|
115
|
+
|
|
116
|
+
// Limit
|
|
117
|
+
const users = await Candy.Mysql.table('users')
|
|
118
|
+
.limit(10)
|
|
119
|
+
.get()
|
|
120
|
+
|
|
121
|
+
// Pagination
|
|
122
|
+
const page = 2
|
|
123
|
+
const perPage = 20
|
|
124
|
+
const users = await Candy.Mysql.table('users')
|
|
125
|
+
.limit((page - 1) * perPage, perPage)
|
|
126
|
+
.get()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Counting Records
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
const userCount = await Candy.Mysql.table('users')
|
|
133
|
+
.where('status', 'active')
|
|
134
|
+
.rows()
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Joins
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
const orders = await Candy.Mysql.table('orders')
|
|
141
|
+
.leftJoin('users', 'orders.user_id', 'users.id')
|
|
142
|
+
.select('orders.*', 'users.name', 'users.email')
|
|
143
|
+
.get()
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Inserting Data
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
// Insert single record
|
|
150
|
+
const result = await Candy.Mysql.table('users').insert({
|
|
151
|
+
name: 'John Doe',
|
|
152
|
+
email: 'john@example.com',
|
|
153
|
+
status: 'active'
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
console.log(result.id) // Inserted ID
|
|
157
|
+
console.log(result.affected) // Affected rows
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Updating Data
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
const result = await Candy.Mysql.table('users')
|
|
164
|
+
.where('id', 1)
|
|
165
|
+
.set({
|
|
166
|
+
name: 'Jane Doe',
|
|
167
|
+
email: 'jane@example.com'
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
console.log(result.affected) // Number of updated rows
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Deleting Data
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
const result = await Candy.Mysql.table('users')
|
|
177
|
+
.where('id', 1)
|
|
178
|
+
.delete()
|
|
179
|
+
|
|
180
|
+
console.log(result.affected) // Number of deleted rows
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Raw Queries
|
|
184
|
+
|
|
185
|
+
For complex queries, use raw SQL:
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
// Raw value in select
|
|
189
|
+
const users = await Candy.Mysql.table('users')
|
|
190
|
+
.select('id', 'name', Candy.Mysql.raw('COUNT(*) as total'))
|
|
191
|
+
.get()
|
|
192
|
+
|
|
193
|
+
// Raw query with parameters (SAFE - recommended)
|
|
194
|
+
const result = await Candy.Mysql.run('SELECT * FROM users WHERE status = ?', ['active'])
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### ⚠️ Security Warning: Mysql.raw()
|
|
198
|
+
|
|
199
|
+
**CRITICAL**: `Mysql.raw()` bypasses ALL SQL injection protection. Only use with hardcoded, trusted values.
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
// ❌ DANGEROUS - Never do this!
|
|
203
|
+
const userInput = await Candy.request('search')
|
|
204
|
+
const users = await Candy.Mysql.table('users')
|
|
205
|
+
.where('name', Candy.Mysql.raw(userInput)) // SQL INJECTION RISK!
|
|
206
|
+
.get()
|
|
207
|
+
|
|
208
|
+
// ✅ SAFE - Use query builder instead
|
|
209
|
+
const userInput = await Candy.request('search')
|
|
210
|
+
const users = await Candy.Mysql.table('users')
|
|
211
|
+
.where('name', 'LIKE', `%${userInput}%`) // Automatically escaped
|
|
212
|
+
.get()
|
|
213
|
+
|
|
214
|
+
// ✅ SAFE - Use with hardcoded values only
|
|
215
|
+
const users = await Candy.Mysql.table('users')
|
|
216
|
+
.select('id', 'name', Candy.Mysql.raw('COUNT(*) as total')) // OK - hardcoded
|
|
217
|
+
.get()
|
|
218
|
+
|
|
219
|
+
// ✅ SAFE - Use parameterized queries for dynamic values
|
|
220
|
+
const status = await Candy.request('status')
|
|
221
|
+
const result = await Candy.Mysql.run(
|
|
222
|
+
'SELECT * FROM users WHERE status = ?',
|
|
223
|
+
[status] // Automatically escaped
|
|
224
|
+
)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**When to use raw()**:
|
|
228
|
+
- Aggregate functions: `COUNT(*)`, `SUM(price)`, `AVG(rating)`
|
|
229
|
+
- Database functions: `NOW()`, `CONCAT()`, `DATE_FORMAT()`
|
|
230
|
+
- Complex expressions that can't be built with query builder
|
|
231
|
+
|
|
232
|
+
**Never use raw() with**:
|
|
233
|
+
- User input from forms, URLs, or cookies
|
|
234
|
+
- Data from external APIs
|
|
235
|
+
- Any untrusted source
|
|
236
|
+
|
|
237
|
+
### Group By
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
const stats = await Candy.Mysql.table('orders')
|
|
241
|
+
.select('user_id', Candy.Mysql.raw('COUNT(*) as order_count'))
|
|
242
|
+
.groupBy('user_id')
|
|
243
|
+
.get()
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Complete Example
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
module.exports = async function (Candy) {
|
|
250
|
+
const page = await Candy.request('page') || 1
|
|
251
|
+
const perPage = 20
|
|
252
|
+
const search = await Candy.request('search')
|
|
253
|
+
|
|
254
|
+
// Build query
|
|
255
|
+
let query = Candy.Mysql.table('products')
|
|
256
|
+
.select('id', 'name', 'price', 'stock')
|
|
257
|
+
.where('status', 'active')
|
|
258
|
+
|
|
259
|
+
// Add search filter
|
|
260
|
+
if (search) {
|
|
261
|
+
query = query.where('name', 'LIKE', `%${search}%`)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Get products
|
|
265
|
+
const products = await query
|
|
266
|
+
.order('created_at', 'desc')
|
|
267
|
+
.limit((page - 1) * perPage, perPage)
|
|
268
|
+
.get()
|
|
269
|
+
|
|
270
|
+
// Get total count
|
|
271
|
+
const totalCount = await Candy.Mysql.table('products')
|
|
272
|
+
.where('status', 'active')
|
|
273
|
+
.rows()
|
|
274
|
+
|
|
275
|
+
Candy.set({
|
|
276
|
+
products: products,
|
|
277
|
+
currentPage: page,
|
|
278
|
+
totalPages: Math.ceil(totalCount / perPage)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
Candy.View.set({
|
|
282
|
+
skeleton: 'main',
|
|
283
|
+
content: 'products.list'
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Best Practices
|
|
289
|
+
|
|
290
|
+
1. **Always use the query builder** - It protects against SQL injection
|
|
291
|
+
2. **Never use Mysql.raw() with user input** - Only use with hardcoded values
|
|
292
|
+
3. **Use parameterized queries** - When using `Mysql.run()`, always pass parameters as array
|
|
293
|
+
4. **Use async/await** - All database methods are asynchronous
|
|
294
|
+
5. **Handle errors** - Wrap database calls in try/catch blocks
|
|
295
|
+
6. **Limit results** - Use `.limit()` to prevent loading too much data
|
|
296
|
+
7. **Select only needed columns** - Use `.select()` instead of selecting all columns
|
|
297
|
+
|
|
298
|
+
### Error Handling
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
module.exports = async function (Candy) {
|
|
302
|
+
try {
|
|
303
|
+
const user = await Candy.Mysql.table('users')
|
|
304
|
+
.where('id', await Candy.request('id'))
|
|
305
|
+
.first()
|
|
306
|
+
|
|
307
|
+
if (!user) {
|
|
308
|
+
Candy.set('error', 'User not found')
|
|
309
|
+
} else {
|
|
310
|
+
Candy.set('user', user)
|
|
311
|
+
}
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error('Database error:', error)
|
|
314
|
+
Candy.set('error', 'An error occurred')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
Candy.View.set({
|
|
318
|
+
skeleton: 'main',
|
|
319
|
+
content: 'user.profile'
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
```
|