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,439 @@
|
|
|
1
|
+
class Internal {
|
|
2
|
+
static #validateField(validator, field, validation, value) {
|
|
3
|
+
const rules = validation.rule.split('|')
|
|
4
|
+
for (const rule of rules) {
|
|
5
|
+
const validatorChain = validator.post(field.name).check(rule)
|
|
6
|
+
if (validation.message) {
|
|
7
|
+
const message = this.replacePlaceholders(validation.message, {
|
|
8
|
+
value: value,
|
|
9
|
+
field: field.name,
|
|
10
|
+
label: field.label || field.placeholder,
|
|
11
|
+
rule: rule
|
|
12
|
+
})
|
|
13
|
+
validatorChain.message(message)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static async register(Candy) {
|
|
19
|
+
const token = await Candy.request('_candy_register_token')
|
|
20
|
+
if (!token) {
|
|
21
|
+
return Candy.return({
|
|
22
|
+
result: {success: false},
|
|
23
|
+
errors: {_candy_form: 'Invalid request'}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const formData = Candy.Request.session(`_register_form_${token}`)
|
|
28
|
+
|
|
29
|
+
if (!formData) {
|
|
30
|
+
return Candy.return({
|
|
31
|
+
result: {success: false},
|
|
32
|
+
errors: {_candy_form: 'Form session expired. Please refresh the page.'}
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (formData.expires < Date.now()) {
|
|
37
|
+
Candy.Request.session(`_register_form_${token}`, null)
|
|
38
|
+
return Candy.return({
|
|
39
|
+
result: {success: false},
|
|
40
|
+
errors: {_candy_form: 'Form session expired. Please refresh the page.'}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (formData.sessionId !== Candy.Request.session('_client')) {
|
|
45
|
+
return Candy.return({
|
|
46
|
+
result: {success: false},
|
|
47
|
+
errors: {_candy_form: 'Invalid session'}
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (formData.userAgent !== Candy.Request.header('user-agent')) {
|
|
52
|
+
return Candy.return({
|
|
53
|
+
result: {success: false},
|
|
54
|
+
errors: {_candy_form: 'Invalid request'}
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (formData.ip !== Candy.Request.ip) {
|
|
59
|
+
return Candy.return({
|
|
60
|
+
result: {success: false},
|
|
61
|
+
errors: {_candy_form: 'Invalid request'}
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const config = formData.config
|
|
66
|
+
const validator = Candy.validator()
|
|
67
|
+
const data = {}
|
|
68
|
+
|
|
69
|
+
const uniqueFields = []
|
|
70
|
+
|
|
71
|
+
for (const field of config.fields) {
|
|
72
|
+
const value = await Candy.request(field.name)
|
|
73
|
+
|
|
74
|
+
for (const validation of field.validations) {
|
|
75
|
+
this.#validateField(validator, field, validation, value)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (field.unique) {
|
|
79
|
+
uniqueFields.push(field.name)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!field.skip) {
|
|
83
|
+
data[field.name] = value
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const set of config.sets) {
|
|
88
|
+
if (set.value !== null) {
|
|
89
|
+
if (set.ifEmpty && data[set.name]) continue
|
|
90
|
+
data[set.name] = set.value
|
|
91
|
+
} else if (set.compute) {
|
|
92
|
+
data[set.name] = await this.computeValue(set.compute, Candy)
|
|
93
|
+
} else if (set.callback) {
|
|
94
|
+
if (typeof Candy.fn[set.callback] === 'function') {
|
|
95
|
+
data[set.name] = await Candy.fn[set.callback](Candy)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (await validator.error()) {
|
|
101
|
+
return validator.result()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const registerResult = await Candy.Auth.register(data, {
|
|
105
|
+
autoLogin: config.autologin !== false,
|
|
106
|
+
uniqueFields: uniqueFields.length > 0 ? uniqueFields : ['email']
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
if (!registerResult.success) {
|
|
110
|
+
if (registerResult.error === 'Database connection not configured') {
|
|
111
|
+
return Candy.return({
|
|
112
|
+
result: {success: false},
|
|
113
|
+
errors: {_candy_form: 'Service temporarily unavailable. Please try again later.'}
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
const errorField = registerResult.field || '_candy_form'
|
|
117
|
+
const errors = {[errorField]: registerResult.error}
|
|
118
|
+
return Candy.return({
|
|
119
|
+
result: {success: false},
|
|
120
|
+
errors: errors
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Candy.Request.session(`_register_form_${token}`, null)
|
|
125
|
+
|
|
126
|
+
return Candy.return({
|
|
127
|
+
result: {
|
|
128
|
+
success: true,
|
|
129
|
+
message: 'Registration successful',
|
|
130
|
+
redirect: config.redirect
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static replacePlaceholders(message, data) {
|
|
136
|
+
if (!message) return message
|
|
137
|
+
|
|
138
|
+
const ruleParts = data.rule ? data.rule.split(':') : []
|
|
139
|
+
const ruleValue = ruleParts[1] || null
|
|
140
|
+
|
|
141
|
+
const placeholders = {
|
|
142
|
+
'{value}': data.value || '',
|
|
143
|
+
'{field}': data.field || '',
|
|
144
|
+
'{label}': data.label || data.field || '',
|
|
145
|
+
'{min}': ruleValue,
|
|
146
|
+
'{max}': ruleValue,
|
|
147
|
+
'{len}': ruleValue,
|
|
148
|
+
'{other}': ruleValue
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let result = message
|
|
152
|
+
for (const [placeholder, value] of Object.entries(placeholders)) {
|
|
153
|
+
if (value !== null) {
|
|
154
|
+
result = result.replace(new RegExp(placeholder.replace(/[{}]/g, '\\$&'), 'g'), value)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return result
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
static async computeValue(type, Candy) {
|
|
162
|
+
switch (type) {
|
|
163
|
+
case 'now':
|
|
164
|
+
return Math.floor(Date.now() / 1000)
|
|
165
|
+
case 'date':
|
|
166
|
+
return new Date().toISOString().split('T')[0]
|
|
167
|
+
case 'datetime':
|
|
168
|
+
return new Date().toISOString()
|
|
169
|
+
case 'timestamp':
|
|
170
|
+
return Date.now()
|
|
171
|
+
case 'ip':
|
|
172
|
+
return Candy.Request.ip
|
|
173
|
+
case 'user_agent':
|
|
174
|
+
return Candy.Request.header('user-agent')
|
|
175
|
+
case 'uuid':
|
|
176
|
+
return require('crypto').randomUUID()
|
|
177
|
+
default:
|
|
178
|
+
return null
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
static async login(Candy) {
|
|
183
|
+
const token = await Candy.request('_candy_login_token')
|
|
184
|
+
if (!token) {
|
|
185
|
+
return Candy.return({
|
|
186
|
+
result: {success: false},
|
|
187
|
+
errors: {_candy_form: 'Invalid request'}
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const formData = Candy.Request.session(`_login_form_${token}`)
|
|
192
|
+
|
|
193
|
+
if (!formData) {
|
|
194
|
+
return Candy.return({
|
|
195
|
+
result: {success: false},
|
|
196
|
+
errors: {_candy_form: 'Form session expired. Please refresh the page.'}
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (formData.expires < Date.now()) {
|
|
201
|
+
Candy.Request.session(`_login_form_${token}`, null)
|
|
202
|
+
return Candy.return({
|
|
203
|
+
result: {success: false},
|
|
204
|
+
errors: {_candy_form: 'Form session expired. Please refresh the page.'}
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (formData.sessionId !== Candy.Request.session('_client')) {
|
|
209
|
+
return Candy.return({
|
|
210
|
+
result: {success: false},
|
|
211
|
+
errors: {_candy_form: 'Invalid session'}
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (formData.userAgent !== Candy.Request.header('user-agent')) {
|
|
216
|
+
return Candy.return({
|
|
217
|
+
result: {success: false},
|
|
218
|
+
errors: {_candy_form: 'Invalid request'}
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (formData.ip !== Candy.Request.ip) {
|
|
223
|
+
return Candy.return({
|
|
224
|
+
result: {success: false},
|
|
225
|
+
errors: {_candy_form: 'Invalid request'}
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const config = formData.config
|
|
230
|
+
const validator = Candy.validator()
|
|
231
|
+
const credentials = {}
|
|
232
|
+
|
|
233
|
+
for (const field of config.fields) {
|
|
234
|
+
const value = await Candy.request(field.name)
|
|
235
|
+
|
|
236
|
+
for (const validation of field.validations) {
|
|
237
|
+
this.#validateField(validator, field, validation, value)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
credentials[field.name] = value
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (await validator.error()) {
|
|
244
|
+
return validator.result()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const loginResult = await Candy.Auth.login(credentials)
|
|
248
|
+
|
|
249
|
+
if (!loginResult.success) {
|
|
250
|
+
if (loginResult.error === 'Database connection not configured') {
|
|
251
|
+
return Candy.return({
|
|
252
|
+
result: {success: false},
|
|
253
|
+
errors: {_candy_form: 'Service temporarily unavailable. Please try again later.'}
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
const errorField = loginResult.field || '_candy_form'
|
|
257
|
+
const errors = {[errorField]: loginResult.error}
|
|
258
|
+
return Candy.return({
|
|
259
|
+
result: {success: false},
|
|
260
|
+
errors: errors
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
Candy.Request.session(`_login_form_${token}`, null)
|
|
265
|
+
|
|
266
|
+
return Candy.return({
|
|
267
|
+
result: {
|
|
268
|
+
success: true,
|
|
269
|
+
message: 'Login successful',
|
|
270
|
+
redirect: config.redirect
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
static async processForm(Candy) {
|
|
276
|
+
const token = await Candy.request('_candy_form_token')
|
|
277
|
+
if (!token) return
|
|
278
|
+
|
|
279
|
+
const formData = Candy.Request.session(`_custom_form_${token}`)
|
|
280
|
+
if (!formData) return
|
|
281
|
+
|
|
282
|
+
if (formData.expires < Date.now()) {
|
|
283
|
+
Candy.Request.session(`_custom_form_${token}`, null)
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (formData.sessionId !== Candy.Request.session('_client')) return
|
|
288
|
+
if (formData.userAgent !== Candy.Request.header('user-agent')) return
|
|
289
|
+
if (formData.ip !== Candy.Request.ip) return
|
|
290
|
+
|
|
291
|
+
const config = formData.config
|
|
292
|
+
const validator = Candy.validator()
|
|
293
|
+
const data = {}
|
|
294
|
+
|
|
295
|
+
const uniqueFields = []
|
|
296
|
+
|
|
297
|
+
for (const field of config.fields) {
|
|
298
|
+
const value = await Candy.request(field.name)
|
|
299
|
+
|
|
300
|
+
for (const validation of field.validations) {
|
|
301
|
+
this.#validateField(validator, field, validation, value)
|
|
302
|
+
|
|
303
|
+
if (validation.rule.includes('unique')) {
|
|
304
|
+
if (!uniqueFields.some(f => f.name === field.name)) {
|
|
305
|
+
uniqueFields.push({name: field.name, message: validation.message})
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!field.skip) {
|
|
311
|
+
data[field.name] = value
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
for (const set of config.sets || []) {
|
|
316
|
+
if (set.value !== undefined && set.value !== null) {
|
|
317
|
+
if (set.ifEmpty && data[set.name] !== undefined && data[set.name] !== null && data[set.name] !== '') continue
|
|
318
|
+
data[set.name] = set.value
|
|
319
|
+
} else if (set.compute) {
|
|
320
|
+
data[set.name] = await this.computeValue(set.compute, Candy)
|
|
321
|
+
} else if (set.callback) {
|
|
322
|
+
if (typeof Candy.fn[set.callback] === 'function') {
|
|
323
|
+
data[set.name] = await Candy.fn[set.callback](Candy)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
Candy.formData = data
|
|
329
|
+
Candy.formConfig = config
|
|
330
|
+
Candy.formValidator = validator
|
|
331
|
+
Candy.formUniqueFields = uniqueFields
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
static async customForm(Candy) {
|
|
335
|
+
const token = await Candy.request('_candy_form_token')
|
|
336
|
+
if (!token) {
|
|
337
|
+
return Candy.return({
|
|
338
|
+
result: {success: false},
|
|
339
|
+
errors: {_candy_form: 'Invalid request'}
|
|
340
|
+
})
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const formData = Candy.Request.session(`_custom_form_${token}`)
|
|
344
|
+
|
|
345
|
+
if (!formData) {
|
|
346
|
+
return Candy.return({
|
|
347
|
+
result: {success: false},
|
|
348
|
+
errors: {_candy_form: 'Form session expired. Please refresh the page.'}
|
|
349
|
+
})
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (formData.expires < Date.now()) {
|
|
353
|
+
Candy.Request.session(`_custom_form_${token}`, null)
|
|
354
|
+
return Candy.return({
|
|
355
|
+
result: {success: false},
|
|
356
|
+
errors: {_candy_form: 'Form session expired. Please refresh the page.'}
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (formData.sessionId !== Candy.Request.session('_client')) {
|
|
361
|
+
return Candy.return({
|
|
362
|
+
result: {success: false},
|
|
363
|
+
errors: {_candy_form: 'Invalid session'}
|
|
364
|
+
})
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (formData.userAgent !== Candy.Request.header('user-agent')) {
|
|
368
|
+
return Candy.return({
|
|
369
|
+
result: {success: false},
|
|
370
|
+
errors: {_candy_form: 'Invalid request'}
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (formData.ip !== Candy.Request.ip) {
|
|
375
|
+
return Candy.return({
|
|
376
|
+
result: {success: false},
|
|
377
|
+
errors: {_candy_form: 'Invalid request'}
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (await Candy.formValidator.error()) {
|
|
382
|
+
return Candy.formValidator.result()
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (Candy.formConfig.table) {
|
|
386
|
+
try {
|
|
387
|
+
const mysql = Candy.Mysql
|
|
388
|
+
|
|
389
|
+
for (const field of Candy.formUniqueFields) {
|
|
390
|
+
if (Candy.formData[field.name] == null) continue
|
|
391
|
+
|
|
392
|
+
const existingRecord = await mysql.query(`SELECT id FROM ?? WHERE ?? = ? LIMIT 1`, [
|
|
393
|
+
Candy.formConfig.table,
|
|
394
|
+
field.name,
|
|
395
|
+
Candy.formData[field.name]
|
|
396
|
+
])
|
|
397
|
+
|
|
398
|
+
if (existingRecord && existingRecord.length > 0) {
|
|
399
|
+
const errorMessage = field.message || `This ${field.name} is already registered`
|
|
400
|
+
return Candy.return({
|
|
401
|
+
result: {success: false},
|
|
402
|
+
errors: {[field.name]: errorMessage}
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
await mysql.query('INSERT INTO ?? SET ?', [Candy.formConfig.table, Candy.formData])
|
|
408
|
+
|
|
409
|
+
Candy.Request.session(`_custom_form_${token}`, null)
|
|
410
|
+
|
|
411
|
+
return Candy.return({
|
|
412
|
+
result: {
|
|
413
|
+
success: true,
|
|
414
|
+
message: Candy.formConfig.successMessage || 'Form submitted successfully!',
|
|
415
|
+
redirect: Candy.formConfig.redirect
|
|
416
|
+
}
|
|
417
|
+
})
|
|
418
|
+
} catch (error) {
|
|
419
|
+
if (error.message === 'Database connection not configured') {
|
|
420
|
+
return Candy.return({
|
|
421
|
+
result: {success: false},
|
|
422
|
+
errors: {_candy_form: 'Database not configured. Please check your config.json'}
|
|
423
|
+
})
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return Candy.return({
|
|
427
|
+
result: {success: false},
|
|
428
|
+
errors: {_candy_form: error.message || 'Database error occurred'}
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
Candy.Request.session(`_custom_form_${token}`, null)
|
|
434
|
+
|
|
435
|
+
return null
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
module.exports = Internal
|