odac 1.4.3 → 1.4.5
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 +2 -2
- package/.agent/rules/memory.md +5 -1
- package/.github/workflows/release.yml +2 -0
- package/.husky/pre-push +0 -1
- package/.kiro/steering/coding.md +27 -0
- package/.kiro/steering/memory.md +56 -0
- package/.kiro/steering/project.md +30 -0
- package/.kiro/steering/workflow.md +16 -0
- package/CHANGELOG.md +98 -0
- package/README.md +2 -1
- package/client/odac.js +121 -2
- package/docs/ai/skills/backend/authentication.md +7 -5
- package/docs/ai/skills/backend/controllers.md +24 -3
- package/docs/ai/skills/backend/forms.md +8 -6
- package/docs/ai/skills/backend/image-processing.md +93 -0
- package/docs/ai/skills/backend/request_response.md +2 -2
- package/docs/ai/skills/backend/routing.md +11 -0
- package/docs/ai/skills/backend/structure.md +1 -1
- package/docs/ai/skills/backend/views.md +34 -9
- package/docs/ai/skills/frontend/navigation.md +45 -1
- package/docs/ai/skills/frontend/realtime.md +18 -2
- package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +24 -0
- package/docs/backend/07-views/03-template-syntax.md +65 -15
- package/docs/backend/07-views/03-variables.md +22 -7
- package/docs/backend/07-views/11-image-optimization.md +197 -0
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +22 -0
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +51 -0
- package/package.json +5 -2
- package/src/Auth.js +8 -4
- package/src/Config.js +5 -0
- package/src/Database/ConnectionFactory.js +16 -0
- package/src/Ipc.js +3 -2
- package/src/Lang.js +17 -10
- package/src/Odac.js +1 -0
- package/src/Request.js +20 -20
- package/src/Route.js +39 -3
- package/src/Validator.js +5 -5
- package/src/View/Image.js +495 -0
- package/src/View.js +4 -0
- package/template/controller/page/about.js +3 -3
- package/template/controller/page/index.js +2 -2
- package/template/public/assets/js/app.js +38 -54
- package/template/skeleton/main.html +4 -4
- package/template/view/content/about.html +64 -60
- package/template/view/content/home.html +148 -175
- package/template/view/css/app.css +46 -0
- package/template/view/footer/main.html +10 -9
- package/template/view/header/main.html +34 -11
- package/test/Auth/verifyMagicLink.test.js +281 -0
- package/test/Client/load.test.js +306 -0
- package/test/Lang/get.test.js +37 -11
- package/test/Odac/image.test.js +61 -0
- package/test/Route/set.test.js +102 -0
- package/test/View/Image/buildFilename.test.js +62 -0
- package/test/View/Image/hash.test.js +59 -0
- package/test/View/Image/isAvailable.test.js +15 -0
- package/test/View/Image/parse.test.js +83 -0
- package/test/View/Image/process.test.js +38 -0
- package/test/View/Image/render.test.js +117 -0
- package/test/View/Image/serve.test.js +56 -0
- package/test/View/Image/url.test.js +53 -0
- package/test/View/constructor.test.js +10 -0
- package/template/public/assets/css/style.css +0 -1835
|
@@ -143,6 +143,57 @@ Odac.action({
|
|
|
143
143
|
|
|
144
144
|
## Animation & Transitions
|
|
145
145
|
|
|
146
|
+
### View Transitions (Recommended)
|
|
147
|
+
|
|
148
|
+
ODAC natively supports the browser's [View Transition API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API). Add the `odac-transition` attribute to any element that should animate between page navigations. No JavaScript configuration is needed.
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<header odac-transition="header">Site Header</header>
|
|
152
|
+
<nav odac-transition="sidebar">Navigation</nav>
|
|
153
|
+
<main>Regular content (updated by AJAX loader)</main>
|
|
154
|
+
<img odac-transition="hero" src="/hero.jpg" alt="Hero" />
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**How it works:**
|
|
158
|
+
- Before navigation, ODAC assigns `view-transition-name` to each `odac-transition` element
|
|
159
|
+
- The browser captures a snapshot of the old state
|
|
160
|
+
- DOM is updated with new content
|
|
161
|
+
- New `odac-transition` elements receive their transition names
|
|
162
|
+
- The browser animates between old and new snapshots
|
|
163
|
+
|
|
164
|
+
**Rules:**
|
|
165
|
+
1. Each `odac-transition` value must be unique within the page (browser requirement)
|
|
166
|
+
2. Elements that persist across pages (e.g., shared header) will morph smoothly
|
|
167
|
+
3. If the browser doesn't support View Transition API, the legacy fade animation runs automatically
|
|
168
|
+
|
|
169
|
+
**CSS Customization:**
|
|
170
|
+
|
|
171
|
+
```css
|
|
172
|
+
/* Crossfade the hero image */
|
|
173
|
+
::view-transition-old(hero) {
|
|
174
|
+
animation: fade-out 0.3s ease;
|
|
175
|
+
}
|
|
176
|
+
::view-transition-new(hero) {
|
|
177
|
+
animation: fade-in 0.3s ease;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Slide the sidebar */
|
|
181
|
+
::view-transition-old(sidebar) {
|
|
182
|
+
animation: slide-out-left 0.25s ease;
|
|
183
|
+
}
|
|
184
|
+
::view-transition-new(sidebar) {
|
|
185
|
+
animation: slide-in-left 0.25s ease;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Default transition for all elements */
|
|
189
|
+
::view-transition-old(*) {
|
|
190
|
+
animation-duration: 0.2s;
|
|
191
|
+
}
|
|
192
|
+
::view-transition-new(*) {
|
|
193
|
+
animation-duration: 0.2s;
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
146
197
|
### Custom Transitions
|
|
147
198
|
|
|
148
199
|
Add custom animations:
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"email": "mail@emre.red",
|
|
8
8
|
"url": "https://emre.red"
|
|
9
9
|
},
|
|
10
|
-
"version": "1.4.
|
|
10
|
+
"version": "1.4.5",
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=18.0.0"
|
|
@@ -24,10 +24,13 @@
|
|
|
24
24
|
"@tailwindcss/cli": "^4.1.18",
|
|
25
25
|
"knex": "^3.1.0",
|
|
26
26
|
"lmdb": "^3.4.4",
|
|
27
|
+
"tailwindcss": "^4.1.18"
|
|
28
|
+
},
|
|
29
|
+
"optionalDependencies": {
|
|
27
30
|
"mysql2": "^3.16.0",
|
|
28
31
|
"pg": "^8.16.3",
|
|
29
32
|
"redis": "^5.10.0",
|
|
30
|
-
"
|
|
33
|
+
"sharp": "^0.33.0"
|
|
31
34
|
},
|
|
32
35
|
"overrides": {
|
|
33
36
|
"tar": "7.5.9",
|
package/src/Auth.js
CHANGED
|
@@ -593,6 +593,7 @@ class Auth {
|
|
|
593
593
|
|
|
594
594
|
// 4. Log in user (or Register if new)
|
|
595
595
|
let user = await Odac.DB[this.#table].where('email', email).first()
|
|
596
|
+
let alreadyLoggedIn = false
|
|
596
597
|
|
|
597
598
|
if (!user) {
|
|
598
599
|
// Auto-Register the user
|
|
@@ -630,12 +631,15 @@ class Auth {
|
|
|
630
631
|
}
|
|
631
632
|
|
|
632
633
|
user = regResult.user
|
|
634
|
+
// register() already performs auto-login by default, skip duplicate login
|
|
635
|
+
alreadyLoggedIn = regResult.autoLogin !== false
|
|
633
636
|
}
|
|
634
637
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
638
|
+
if (!alreadyLoggedIn) {
|
|
639
|
+
const loginData = {}
|
|
640
|
+
loginData[primaryKey] = user[primaryKey]
|
|
641
|
+
await this.login(loginData)
|
|
642
|
+
}
|
|
639
643
|
|
|
640
644
|
return {success: true, user: user}
|
|
641
645
|
}
|
package/src/Config.js
CHANGED
|
@@ -35,6 +35,13 @@ function buildConnectionConfig(db, client) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** @type {Record<string, string>} Maps ODAC db type to the npm package that must be installed */
|
|
39
|
+
const DRIVER_PACKAGES = {
|
|
40
|
+
pg: 'pg',
|
|
41
|
+
mysql2: 'mysql2',
|
|
42
|
+
sqlite3: 'sqlite3'
|
|
43
|
+
}
|
|
44
|
+
|
|
38
45
|
/**
|
|
39
46
|
* Creates knex connections map from ODAC database config.
|
|
40
47
|
* Why: Centralizes zero-config connection bootstrap used by runtime and migration CLI.
|
|
@@ -51,6 +58,15 @@ function buildConnections(databaseConfig) {
|
|
|
51
58
|
const client = resolveClient(db.type)
|
|
52
59
|
const connection = buildConnectionConfig(db, client)
|
|
53
60
|
|
|
61
|
+
const pkg = DRIVER_PACKAGES[client]
|
|
62
|
+
if (pkg) {
|
|
63
|
+
try {
|
|
64
|
+
require(pkg)
|
|
65
|
+
} catch {
|
|
66
|
+
throw new Error(`Database driver "${pkg}" is not installed. Run: npm install ${pkg}`)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
connections[key] = knex({
|
|
55
71
|
client,
|
|
56
72
|
connection,
|
package/src/Ipc.js
CHANGED
|
@@ -122,8 +122,9 @@ class Ipc extends EventEmitter {
|
|
|
122
122
|
this.redis = Redis.createClient(Odac.Config.database?.redis?.[this.config.redis || 'default'] || {})
|
|
123
123
|
await this.redis.connect()
|
|
124
124
|
} catch (e) {
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
if (e.code === 'MODULE_NOT_FOUND') {
|
|
126
|
+
throw new Error('IPC Redis driver requires the "redis" package. Run: npm install redis')
|
|
127
|
+
}
|
|
127
128
|
throw e
|
|
128
129
|
}
|
|
129
130
|
}
|
package/src/Lang.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
1
|
+
const fs = require('node:fs')
|
|
2
|
+
const fsPromises = fs.promises
|
|
2
3
|
|
|
3
4
|
class Lang {
|
|
4
5
|
#odac
|
|
@@ -10,7 +11,7 @@ class Lang {
|
|
|
10
11
|
this.set()
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
get(...args) {
|
|
14
|
+
async get(...args) {
|
|
14
15
|
if (typeof args[0] !== 'string') return args[0]
|
|
15
16
|
if (!this.#data[args[0]]) {
|
|
16
17
|
this.#data[args[0]] = args[0]
|
|
@@ -34,11 +35,11 @@ class Lang {
|
|
|
34
35
|
return str
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
#save() {
|
|
38
|
+
async #save() {
|
|
38
39
|
if (!this.#lang) return
|
|
39
|
-
|
|
40
|
-
if (!fs.existsSync(
|
|
41
|
-
|
|
40
|
+
const langDir = __dir + '/storage/language/'
|
|
41
|
+
if (!fs.existsSync(langDir)) await fsPromises.mkdir(langDir, {recursive: true})
|
|
42
|
+
await fsPromises.writeFile(langDir + this.#lang + '.json', JSON.stringify(this.#data, null, 4))
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
set(lang) {
|
|
@@ -49,15 +50,21 @@ class Lang {
|
|
|
49
50
|
this.#odac.Request.header('ACCEPT-LANGUAGE') &&
|
|
50
51
|
this.#odac.Request.header('ACCEPT-LANGUAGE').length > 1
|
|
51
52
|
) {
|
|
52
|
-
lang = this.#odac.Request.header('ACCEPT-LANGUAGE').
|
|
53
|
+
lang = this.#odac.Request.header('ACCEPT-LANGUAGE').slice(0, 2)
|
|
53
54
|
} else {
|
|
54
55
|
lang = this.#odac.Config.lang?.default || 'en'
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
this.#lang = lang
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
const langFile = __dir + '/storage/language/' + lang + '.json'
|
|
60
|
+
if (fs.existsSync(langFile)) {
|
|
61
|
+
// Use Sync read here only because it's during initialization/request entry
|
|
62
|
+
// and we need to block briefly to ensure data is ready before logic proceeds.
|
|
63
|
+
// In a high-load system, this should Ideally be pre-cached.
|
|
64
|
+
this.#data = JSON.parse(fs.readFileSync(langFile, 'utf8'))
|
|
65
|
+
} else {
|
|
66
|
+
this.#data = {}
|
|
67
|
+
}
|
|
61
68
|
}
|
|
62
69
|
}
|
|
63
70
|
|
package/src/Odac.js
CHANGED
|
@@ -62,6 +62,7 @@ module.exports = {
|
|
|
62
62
|
_odac.Server = require('./Server.js')
|
|
63
63
|
_odac.Storage = require('./Storage.js')
|
|
64
64
|
_odac.Var = (...args) => new (require('./Var.js'))(...args)
|
|
65
|
+
_odac.image = (src, options) => require('./View/Image.js').url(src, options)
|
|
65
66
|
|
|
66
67
|
if (req) {
|
|
67
68
|
_odac.setInterval = function (callback, delay, ...args) {
|
package/src/Request.js
CHANGED
|
@@ -32,17 +32,17 @@ class OdacRequest {
|
|
|
32
32
|
delete this.req.headers['x-odac-connection-ssl']
|
|
33
33
|
delete this.req.headers['x-odac-connection-remoteaddress']
|
|
34
34
|
let route = req.headers.host.split('.')[0]
|
|
35
|
-
if (!Odac.Route.routes[route]) route = 'www'
|
|
35
|
+
if (!global.Odac.Route.routes[route]) route = 'www'
|
|
36
36
|
this.route = route
|
|
37
37
|
if (this.res) {
|
|
38
38
|
if (typeof this.#odac.setTimeout === 'function') {
|
|
39
|
-
this.#timeout = this.#odac.setTimeout(() => !this.res.finished && this.abort(408), Odac.Config.request.timeout)
|
|
39
|
+
this.#timeout = this.#odac.setTimeout(() => !this.res.finished && this.abort(408), global.Odac.Config.request.timeout)
|
|
40
40
|
} else {
|
|
41
|
-
this.#timeout = setTimeout(() => !this.res.finished && this.abort(408), Odac.Config.request.timeout)
|
|
41
|
+
this.#timeout = setTimeout(() => !this.res.finished && this.abort(408), global.Odac.Config.request.timeout)
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
this.#data()
|
|
45
|
-
if (!Odac.Request) Odac.Request = {}
|
|
45
|
+
if (!global.Odac.Request) global.Odac.Request = {}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// - ABORT REQUEST
|
|
@@ -50,11 +50,11 @@ class OdacRequest {
|
|
|
50
50
|
this.status(code)
|
|
51
51
|
let result = {401: 'Unauthorized', 404: 'Not Found', 408: 'Request Timeout'}[code] ?? null
|
|
52
52
|
if (
|
|
53
|
-
Odac.Route.routes[this.route].error &&
|
|
54
|
-
Odac.Route.routes[this.route].error[code] &&
|
|
55
|
-
typeof Odac.Route.routes[this.route].error[code].cache === 'function'
|
|
53
|
+
global.Odac.Route.routes[this.route].error &&
|
|
54
|
+
global.Odac.Route.routes[this.route].error[code] &&
|
|
55
|
+
typeof global.Odac.Route.routes[this.route].error[code].cache === 'function'
|
|
56
56
|
)
|
|
57
|
-
result = await Odac.Route.routes[this.route].error[code].cache(this.#odac)
|
|
57
|
+
result = await global.Odac.Route.routes[this.route].error[code].cache(this.#odac)
|
|
58
58
|
this.end(result)
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -250,30 +250,30 @@ class OdacRequest {
|
|
|
250
250
|
.update(this.req.headers['user-agent'] ?? '.')
|
|
251
251
|
.digest('hex')
|
|
252
252
|
let pub = this.cookie('odac_session')
|
|
253
|
-
if (!pub || !Odac.Storage.get(`sess:${pub}:${pri}:_created`)) {
|
|
253
|
+
if (!pub || !global.Odac.Storage.get(`sess:${pub}:${pri}:_created`)) {
|
|
254
254
|
const lockKey = `lock:${this.ip}:${pri}`
|
|
255
255
|
const now = Date.now()
|
|
256
256
|
|
|
257
|
-
const existingLock = Odac.Storage.get(lockKey)
|
|
257
|
+
const existingLock = global.Odac.Storage.get(lockKey)
|
|
258
258
|
if (existingLock) {
|
|
259
|
-
if (now - existingLock.timestamp < 2000 && Odac.Storage.get(`sess:${existingLock.sessionId}:${pri}:_created`)) {
|
|
259
|
+
if (now - existingLock.timestamp < 2000 && global.Odac.Storage.get(`sess:${existingLock.sessionId}:${pri}:_created`)) {
|
|
260
260
|
pub = existingLock.sessionId
|
|
261
261
|
} else {
|
|
262
|
-
Odac.Storage.remove(lockKey)
|
|
262
|
+
global.Odac.Storage.remove(lockKey)
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
if (!pub) {
|
|
267
267
|
do {
|
|
268
268
|
pub = nodeCrypto.randomBytes(16).toString('hex')
|
|
269
|
-
} while (Odac.Storage.get(`sess:${pub}:${pri}:_created`))
|
|
270
|
-
Odac.Storage.put(lockKey, {sessionId: pub, timestamp: now})
|
|
271
|
-
Odac.Storage.put(`sess:${pub}:${pri}:_created`, now)
|
|
269
|
+
} while (global.Odac.Storage.get(`sess:${pub}:${pri}:_created`))
|
|
270
|
+
global.Odac.Storage.put(lockKey, {sessionId: pub, timestamp: now})
|
|
271
|
+
global.Odac.Storage.put(`sess:${pub}:${pri}:_created`, now)
|
|
272
272
|
this.cookie('odac_session', `${pub}`)
|
|
273
273
|
setTimeout(() => {
|
|
274
|
-
const lock = Odac.Storage.get(lockKey)
|
|
274
|
+
const lock = global.Odac.Storage.get(lockKey)
|
|
275
275
|
if (lock?.timestamp === now) {
|
|
276
|
-
Odac.Storage.remove(lockKey)
|
|
276
|
+
global.Odac.Storage.remove(lockKey)
|
|
277
277
|
}
|
|
278
278
|
}, 2000)
|
|
279
279
|
}
|
|
@@ -282,15 +282,15 @@ class OdacRequest {
|
|
|
282
282
|
const dbKey = `sess:${pub}:${pri}:${key}`
|
|
283
283
|
if (value === undefined) {
|
|
284
284
|
if (Object.prototype.hasOwnProperty.call(this.#sessions, dbKey)) return this.#sessions[dbKey]
|
|
285
|
-
const dbValue = Odac.Storage.get(dbKey) ?? null
|
|
285
|
+
const dbValue = global.Odac.Storage.get(dbKey) ?? null
|
|
286
286
|
return dbValue
|
|
287
287
|
} else if (value === null) {
|
|
288
288
|
delete this.#sessions[dbKey]
|
|
289
289
|
delete this.#sessions[dbKey]
|
|
290
|
-
Odac.Storage.remove(dbKey)
|
|
290
|
+
global.Odac.Storage.remove(dbKey)
|
|
291
291
|
} else {
|
|
292
292
|
this.#sessions[dbKey] = value
|
|
293
|
-
Odac.Storage.put(dbKey, value)
|
|
293
|
+
global.Odac.Storage.put(dbKey, value)
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
package/src/Route.js
CHANGED
|
@@ -4,10 +4,11 @@ const path = require('path')
|
|
|
4
4
|
|
|
5
5
|
const Cron = require('./Route/Cron.js')
|
|
6
6
|
const Internal = require('./Route/Internal.js')
|
|
7
|
+
const Image = require('./View/Image.js')
|
|
7
8
|
const MiddlewareChain = require('./Route/Middleware.js')
|
|
8
9
|
const {WebSocketServer} = require('./WebSocket.js')
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
let routes2 = {}
|
|
11
12
|
const mime = require('./Route/MimeTypes.js')
|
|
12
13
|
|
|
13
14
|
class Route {
|
|
@@ -586,6 +587,29 @@ class Route {
|
|
|
586
587
|
{token: false}
|
|
587
588
|
)
|
|
588
589
|
|
|
590
|
+
this.set(
|
|
591
|
+
'GET',
|
|
592
|
+
'/_odac/img/{file}',
|
|
593
|
+
async Odac => {
|
|
594
|
+
const filename = Odac.Request.data.url.file
|
|
595
|
+
if (!filename) return Odac.Request.abort(404)
|
|
596
|
+
|
|
597
|
+
// Validate filename format: {name}-{dimension}-{hash8}.{ext}
|
|
598
|
+
if (!/^[\w-]+-(?:\d+|o)-[a-f0-9]{8}\.(webp|avif|png|jpeg|jpg|tiff)$/.test(filename)) {
|
|
599
|
+
return Odac.Request.abort(404)
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const result = await Image.serve(filename)
|
|
603
|
+
if (!result) return Odac.Request.abort(404)
|
|
604
|
+
|
|
605
|
+
Odac.Request.header('Content-Type', result.type)
|
|
606
|
+
Odac.Request.header('Content-Length', result.size)
|
|
607
|
+
Odac.Request.header('Cache-Control', 'public, max-age=31536000, immutable')
|
|
608
|
+
return result.stream
|
|
609
|
+
},
|
|
610
|
+
{token: false}
|
|
611
|
+
)
|
|
612
|
+
|
|
589
613
|
delete Odac.Route.buff
|
|
590
614
|
}
|
|
591
615
|
|
|
@@ -679,6 +703,13 @@ class Route {
|
|
|
679
703
|
// File error, proceed to reload or re-set
|
|
680
704
|
}
|
|
681
705
|
} else {
|
|
706
|
+
// Update inline function reference on hot reload
|
|
707
|
+
this.routes[Odac.Route.buff][type][url].cache = file
|
|
708
|
+
this.routes[Odac.Route.buff][type][url].file = file
|
|
709
|
+
this.routes[Odac.Route.buff][type][url].mtime = Date.now()
|
|
710
|
+
this.routes[Odac.Route.buff][type][url].middlewares = capturedMiddlewares
|
|
711
|
+
this.routes[Odac.Route.buff][type][url].token = options.token ?? true
|
|
712
|
+
this.routes[Odac.Route.buff][type][url].action = action
|
|
682
713
|
return
|
|
683
714
|
}
|
|
684
715
|
}
|
|
@@ -709,9 +740,14 @@ class Route {
|
|
|
709
740
|
this.routes[Odac.Route.buff][type][url].action = action
|
|
710
741
|
|
|
711
742
|
this.routes[Odac.Route.buff][type][url].middlewares = capturedMiddlewares
|
|
712
|
-
} catch {
|
|
743
|
+
} catch (err) {
|
|
713
744
|
if (file && typeof file === 'string') {
|
|
714
|
-
|
|
745
|
+
if (err.code === 'ENOENT') {
|
|
746
|
+
console.error(`\x1b[31m[Odac]\x1b[0m Controller not found: \x1b[33m${path}\x1b[0m`)
|
|
747
|
+
} else {
|
|
748
|
+
console.error(`\x1b[31m[Odac]\x1b[0m Failed to load controller: \x1b[33m${path}\x1b[0m`)
|
|
749
|
+
console.error(`\x1b[31m[Odac]\x1b[0m ${err.message}`)
|
|
750
|
+
}
|
|
715
751
|
}
|
|
716
752
|
}
|
|
717
753
|
}
|
package/src/Validator.js
CHANGED
|
@@ -343,10 +343,10 @@ class Validator {
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
async brute(maxAttempts = 5) {
|
|
346
|
-
const ip = this.#request.ip
|
|
346
|
+
const ip = this.#request.ip
|
|
347
347
|
const now = new Date().toISOString().slice(0, 13).replace(/[-:T]/g, '')
|
|
348
|
-
const page = this.#request.
|
|
349
|
-
const storage = this.#odac.
|
|
348
|
+
const page = this.#request.url.split('?')[0]
|
|
349
|
+
const storage = this.#odac.Storage
|
|
350
350
|
const validation = storage.get('validation') || {}
|
|
351
351
|
|
|
352
352
|
this.#name = '_odac_form'
|
|
@@ -361,12 +361,12 @@ class Validator {
|
|
|
361
361
|
|
|
362
362
|
if (validation.brute[now][page][ip] >= maxAttempts) {
|
|
363
363
|
this.#message['_odac_form'] = this.#odac.Lang
|
|
364
|
-
? this.#odac.Lang.get('Too many failed attempts. Please try again later.')
|
|
364
|
+
? await this.#odac.Lang.get('Too many failed attempts. Please try again later.')
|
|
365
365
|
: 'Too many failed attempts. Please try again later.'
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
storage.
|
|
369
|
+
storage.put('validation', validation)
|
|
370
370
|
return this
|
|
371
371
|
}
|
|
372
372
|
}
|