@ditojs/server 2.0.4 → 2.1.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/package.json +11 -13
- package/src/app/Application.js +228 -212
- package/src/app/Validator.js +53 -43
- package/src/cli/console.js +6 -4
- package/src/cli/db/createMigration.js +59 -30
- package/src/cli/db/migrate.js +6 -4
- package/src/cli/db/reset.js +8 -5
- package/src/cli/db/rollback.js +6 -4
- package/src/cli/db/seed.js +2 -1
- package/src/cli/index.js +1 -1
- package/src/controllers/AdminController.js +100 -84
- package/src/controllers/CollectionController.js +37 -30
- package/src/controllers/Controller.js +83 -43
- package/src/controllers/ControllerAction.js +27 -15
- package/src/controllers/ModelController.js +4 -1
- package/src/controllers/RelationController.js +19 -21
- package/src/controllers/UsersController.js +3 -4
- package/src/decorators/parameters.js +3 -1
- package/src/decorators/scope.js +1 -1
- package/src/errors/ControllerError.js +2 -1
- package/src/errors/DatabaseError.js +20 -11
- package/src/graph/DitoGraphProcessor.js +48 -40
- package/src/graph/expression.js +6 -8
- package/src/graph/graph.js +20 -11
- package/src/lib/EventEmitter.js +12 -12
- package/src/middleware/handleConnectMiddleware.js +16 -10
- package/src/middleware/handleError.js +6 -5
- package/src/middleware/handleSession.js +78 -0
- package/src/middleware/handleUser.js +2 -2
- package/src/middleware/index.js +2 -0
- package/src/middleware/logRequests.js +3 -3
- package/src/middleware/setupRequestStorage.js +14 -0
- package/src/mixins/AssetMixin.js +62 -58
- package/src/mixins/SessionMixin.js +13 -10
- package/src/mixins/TimeStampedMixin.js +33 -29
- package/src/mixins/UserMixin.js +130 -116
- package/src/models/Model.js +245 -194
- package/src/models/definitions/filters.js +14 -13
- package/src/query/QueryBuilder.js +252 -195
- package/src/query/QueryFilters.js +3 -3
- package/src/query/QueryParameters.js +2 -2
- package/src/query/Registry.js +8 -10
- package/src/schema/keywords/_validate.js +10 -8
- package/src/schema/properties.test.js +247 -206
- package/src/schema/relations.js +42 -20
- package/src/schema/relations.test.js +36 -19
- package/src/services/Service.js +8 -14
- package/src/storage/S3Storage.js +5 -3
- package/src/storage/Storage.js +16 -14
- package/src/utils/function.js +7 -4
- package/src/utils/function.test.js +30 -6
- package/src/utils/object.test.js +5 -1
- package/types/index.d.ts +244 -257
- package/src/app/SessionStore.js +0 -31
|
@@ -76,9 +76,11 @@ export class DitoGraphProcessor {
|
|
|
76
76
|
// since `relation.graphOptions` is across insert / upsert & co.,
|
|
77
77
|
// but not all of them use all options (insert defines less).
|
|
78
78
|
for (const key in this.options) {
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
if (
|
|
80
|
+
key in graphOptions &&
|
|
81
|
+
graphOptions[key] !== this.options[key] &&
|
|
82
|
+
!this.overrides[key]
|
|
83
|
+
) {
|
|
82
84
|
this.numOverrides++
|
|
83
85
|
this.overrides[key] = []
|
|
84
86
|
}
|
|
@@ -104,41 +106,45 @@ export class DitoGraphProcessor {
|
|
|
104
106
|
processOverrides() {
|
|
105
107
|
const expr = modelGraphToExpression(this.data)
|
|
106
108
|
|
|
107
|
-
const processExpression =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const extra = relation.through?.extra
|
|
123
|
-
if (extra?.length > 0) {
|
|
124
|
-
this.extras[relationPath] = extra
|
|
109
|
+
const processExpression = (
|
|
110
|
+
expr,
|
|
111
|
+
modelClass,
|
|
112
|
+
relation,
|
|
113
|
+
relationPath = ''
|
|
114
|
+
) => {
|
|
115
|
+
if (relation) {
|
|
116
|
+
const graphOptions = this.getGraphOptions(relation)
|
|
117
|
+
// Loop through all override options, figure out their settings for
|
|
118
|
+
// the current relation and build relation expression arrays for each
|
|
119
|
+
// override reflecting their nested settings in arrays of expressions.
|
|
120
|
+
for (const key in this.overrides) {
|
|
121
|
+
const option = graphOptions[key] ?? this.options[key]
|
|
122
|
+
if (option) {
|
|
123
|
+
this.overrides[key].push(relationPath)
|
|
125
124
|
}
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const { relatedModelClass } = relationInstances[key]
|
|
133
|
-
processExpression(
|
|
134
|
-
childExpr,
|
|
135
|
-
relatedModelClass,
|
|
136
|
-
relations[key],
|
|
137
|
-
appendPath(relationPath, '.', key)
|
|
138
|
-
)
|
|
127
|
+
// Also collect any many-to-many pivot table extra properties.
|
|
128
|
+
const extra = relation.through?.extra
|
|
129
|
+
if (extra?.length > 0) {
|
|
130
|
+
this.extras[relationPath] = extra
|
|
139
131
|
}
|
|
140
132
|
}
|
|
141
133
|
|
|
134
|
+
const { relations } = modelClass.definition
|
|
135
|
+
const relationInstances = modelClass.getRelations()
|
|
136
|
+
for (const key in expr) {
|
|
137
|
+
const childExpr = expr[key]
|
|
138
|
+
const { relatedModelClass } = relationInstances[key]
|
|
139
|
+
processExpression(
|
|
140
|
+
childExpr,
|
|
141
|
+
relatedModelClass,
|
|
142
|
+
relations[key],
|
|
143
|
+
appendPath(relationPath, '.', key)
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
142
148
|
processExpression(expr, this.rootModelClass)
|
|
143
149
|
}
|
|
144
150
|
|
|
@@ -147,9 +153,9 @@ export class DitoGraphProcessor {
|
|
|
147
153
|
if (relationPath !== '') {
|
|
148
154
|
const { relate } = this.overrides
|
|
149
155
|
return relate
|
|
150
|
-
// See if the relate overrides contain this particular relation-Path
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
? // See if the relate overrides contain this particular relation-Path
|
|
157
|
+
// and only remove and restore relation data if relate is to be used
|
|
158
|
+
relate.includes(relationPath)
|
|
153
159
|
: this.options.relate
|
|
154
160
|
}
|
|
155
161
|
}
|
|
@@ -189,11 +195,13 @@ export class DitoGraphProcessor {
|
|
|
189
195
|
return copy
|
|
190
196
|
} else if (isArray(data)) {
|
|
191
197
|
// Potentially a has-many relation, so keep processing relates:
|
|
192
|
-
return data.map((entry, index) =>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
198
|
+
return data.map((entry, index) =>
|
|
199
|
+
this.processRelates(
|
|
200
|
+
entry,
|
|
201
|
+
relationPath,
|
|
202
|
+
appendPath(dataPath, '/', index)
|
|
203
|
+
)
|
|
204
|
+
)
|
|
197
205
|
}
|
|
198
206
|
}
|
|
199
207
|
return data
|
package/src/graph/expression.js
CHANGED
|
@@ -26,14 +26,12 @@ export function collectExpressionPaths(expr) {
|
|
|
26
26
|
|
|
27
27
|
export function expressionPathToString(path, start = 0) {
|
|
28
28
|
return (start ? path.slice(start) : path)
|
|
29
|
-
.map(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
)
|
|
29
|
+
.map(({ relation, alias, modify }) => {
|
|
30
|
+
const expr = alias ? `${relation} as ${alias}` : relation
|
|
31
|
+
return modify.length > 0
|
|
32
|
+
? `${expr}(${modify.join(', ')})`
|
|
33
|
+
: expr
|
|
34
|
+
})
|
|
37
35
|
.join('.')
|
|
38
36
|
}
|
|
39
37
|
|
package/src/graph/graph.js
CHANGED
|
@@ -127,13 +127,21 @@ export async function populateGraph(rootModelClass, graph, expr, trx) {
|
|
|
127
127
|
// contain path entries with relation names and modify settings.
|
|
128
128
|
|
|
129
129
|
const grouped = {}
|
|
130
|
-
const addToGroup =
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
130
|
+
const addToGroup = (
|
|
131
|
+
item,
|
|
132
|
+
modelClass,
|
|
133
|
+
isReference,
|
|
134
|
+
modify,
|
|
135
|
+
relation,
|
|
136
|
+
expr
|
|
137
|
+
) => {
|
|
138
|
+
const id = item.$id()
|
|
139
|
+
if (id != null) {
|
|
140
|
+
// Group models by model-name + modify + expr, for faster loading:
|
|
141
|
+
const key = `${modelClass.name}_${modify}_${expr || ''}`
|
|
142
|
+
const group = (
|
|
143
|
+
grouped[key] ||
|
|
144
|
+
(grouped[key] = {
|
|
137
145
|
modelClass,
|
|
138
146
|
modify,
|
|
139
147
|
relation,
|
|
@@ -142,11 +150,12 @@ export async function populateGraph(rootModelClass, graph, expr, trx) {
|
|
|
142
150
|
ids: [],
|
|
143
151
|
modelsById: {}
|
|
144
152
|
})
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
153
|
+
)
|
|
154
|
+
group.targets.push({ item, isReference })
|
|
155
|
+
// Collect ids to be loaded for the targets.
|
|
156
|
+
group.ids.push(id)
|
|
149
157
|
}
|
|
158
|
+
}
|
|
150
159
|
|
|
151
160
|
for (const path of collectExpressionPaths(expr)) {
|
|
152
161
|
let modelClass = rootModelClass
|
package/src/lib/EventEmitter.js
CHANGED
|
@@ -25,6 +25,18 @@ export class EventEmitter extends EventEmitter2 {
|
|
|
25
25
|
return this.emitAsync(event, ...args)
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
on(event, callback) {
|
|
29
|
+
return this._handle('on', event, callback)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
off(event, callback) {
|
|
33
|
+
return this._handle('off', event, callback)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
once(event, callback) {
|
|
37
|
+
return this._handle('once', event, callback)
|
|
38
|
+
}
|
|
39
|
+
|
|
28
40
|
_handle(method, event, callback) {
|
|
29
41
|
if (isString(event)) {
|
|
30
42
|
super[method](event, callback)
|
|
@@ -40,18 +52,6 @@ export class EventEmitter extends EventEmitter2 {
|
|
|
40
52
|
return this
|
|
41
53
|
}
|
|
42
54
|
|
|
43
|
-
on(event, callback) {
|
|
44
|
-
return this._handle('on', event, callback)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
off(event, callback) {
|
|
48
|
-
return this._handle('off', event, callback)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
once(event, callback) {
|
|
52
|
-
return this._handle('once', event, callback)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
55
|
static mixin(target) {
|
|
56
56
|
Object.defineProperties(target, properties)
|
|
57
57
|
}
|
|
@@ -39,16 +39,22 @@ export function handleConnectMiddleware(middleware, {
|
|
|
39
39
|
}
|
|
40
40
|
if (isArray(headers)) {
|
|
41
41
|
// Convert raw headers array to object.
|
|
42
|
-
headers = Object.fromEntries(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
headers = Object.fromEntries(
|
|
43
|
+
headers.reduce(
|
|
44
|
+
// Translate raw array to [field, value] tuples.
|
|
45
|
+
(entries, value, index) => {
|
|
46
|
+
if (index & 1) {
|
|
47
|
+
// Odd: value
|
|
48
|
+
entries[entries.length - 1].push(value)
|
|
49
|
+
} else {
|
|
50
|
+
// Even: field
|
|
51
|
+
entries.push([value])
|
|
52
|
+
}
|
|
53
|
+
return entries
|
|
54
|
+
},
|
|
55
|
+
[]
|
|
56
|
+
)
|
|
57
|
+
)
|
|
52
58
|
}
|
|
53
59
|
if (isObject(headers)) {
|
|
54
60
|
ctx.set(headers)
|
|
@@ -11,11 +11,12 @@ export function handleError() {
|
|
|
11
11
|
// error. But don't do so if the request actually went to js file.
|
|
12
12
|
if (ctx.accepts('json') && !ctx.request.path.endsWith('.js')) {
|
|
13
13
|
// Format error as JSON
|
|
14
|
-
ctx.body =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
ctx.body =
|
|
15
|
+
err instanceof ResponseError
|
|
16
|
+
? err.toJSON()
|
|
17
|
+
: {
|
|
18
|
+
message: err.message || 'An error has occurred.'
|
|
19
|
+
}
|
|
19
20
|
} else {
|
|
20
21
|
// TODO: Consider handling html and xml responses also, see:
|
|
21
22
|
// https://github.com/strongloop/strong-error-handler/blob/master/lib/send-html.js
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import session from 'koa-session'
|
|
2
|
+
import compose from 'koa-compose'
|
|
3
|
+
import { isString } from '@ditojs/utils'
|
|
4
|
+
|
|
5
|
+
export function handleSession(app, {
|
|
6
|
+
modelClass,
|
|
7
|
+
autoCommit = true,
|
|
8
|
+
...options
|
|
9
|
+
} = {}) {
|
|
10
|
+
if (modelClass) {
|
|
11
|
+
// Create a ContextStore that resolved the specified model class,
|
|
12
|
+
// uses it to persist and retrieve the session, and automatically
|
|
13
|
+
// binds all db operations to `ctx.transaction`, if it is set.
|
|
14
|
+
// eslint-disable-next-line new-cap
|
|
15
|
+
options.ContextStore = createSessionStore(modelClass)
|
|
16
|
+
}
|
|
17
|
+
options.autoCommit = false
|
|
18
|
+
return compose([
|
|
19
|
+
session(options, app),
|
|
20
|
+
async (ctx, next) => {
|
|
21
|
+
// Get hold of `session` now, since it may not be available from the
|
|
22
|
+
// `ctx` if the session is destroyed.
|
|
23
|
+
const {
|
|
24
|
+
session,
|
|
25
|
+
route: { transacted }
|
|
26
|
+
} = ctx
|
|
27
|
+
try {
|
|
28
|
+
await next()
|
|
29
|
+
if (autoCommit && transacted) {
|
|
30
|
+
// When transacted, only commit when there are no errors. Otherwise,
|
|
31
|
+
// the commit will fail and the original error will be lost.
|
|
32
|
+
await session.commit()
|
|
33
|
+
}
|
|
34
|
+
} finally {
|
|
35
|
+
// When not transacted, keep the original behavior of always
|
|
36
|
+
// committing.
|
|
37
|
+
if (autoCommit && !transacted) {
|
|
38
|
+
await session.commit()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
])
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const createSessionStore = modelClass =>
|
|
46
|
+
class SessionStore {
|
|
47
|
+
constructor(ctx) {
|
|
48
|
+
this.ctx = ctx
|
|
49
|
+
this.modelClass = isString(modelClass)
|
|
50
|
+
? ctx.app.models[modelClass]
|
|
51
|
+
: modelClass
|
|
52
|
+
if (!this.modelClass) {
|
|
53
|
+
throw new Error(`Unable to find model class: '${modelClass}'`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
query() {
|
|
58
|
+
return this.modelClass.query(this.ctx.transaction)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async get(id) {
|
|
62
|
+
const session = await this.query().findById(id)
|
|
63
|
+
return session?.value || {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async set(id, value) {
|
|
67
|
+
await this.query()
|
|
68
|
+
.findById(id)
|
|
69
|
+
.upsert({
|
|
70
|
+
...this.modelClass.getReference(id),
|
|
71
|
+
value
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async destroy(key) {
|
|
76
|
+
await this.query().deleteById(key)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -7,13 +7,13 @@ export function handleUser() {
|
|
|
7
7
|
// on the user model:
|
|
8
8
|
const { login, logout } = ctx
|
|
9
9
|
|
|
10
|
-
ctx.login = ctx.logIn = async function(user, options = {}) {
|
|
10
|
+
ctx.login = ctx.logIn = async function (user, options = {}) {
|
|
11
11
|
await user.$emit('before:login', options)
|
|
12
12
|
await login.call(this, user, options)
|
|
13
13
|
await user.$emit('after:login', options)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
ctx.logout = ctx.logOut = async function(options = {}) {
|
|
16
|
+
ctx.logout = ctx.logOut = async function (options = {}) {
|
|
17
17
|
const { user } = ctx.state
|
|
18
18
|
await user?.$emit('before:logout', options)
|
|
19
19
|
await logout.call(this, options)
|
package/src/middleware/index.js
CHANGED
|
@@ -4,5 +4,7 @@ export * from './findRoute.js'
|
|
|
4
4
|
export * from './handleConnectMiddleware.js'
|
|
5
5
|
export * from './handleError.js'
|
|
6
6
|
export * from './handleRoute.js'
|
|
7
|
+
export * from './handleSession.js'
|
|
7
8
|
export * from './handleUser.js'
|
|
8
9
|
export * from './logRequests.js'
|
|
10
|
+
export * from './setupRequestStorage.js'
|
|
@@ -5,9 +5,9 @@ import bytes from 'bytes'
|
|
|
5
5
|
import pico from 'picocolors'
|
|
6
6
|
import Counter from 'passthrough-counter'
|
|
7
7
|
|
|
8
|
-
export function logRequests({
|
|
8
|
+
export function logRequests({ ignoreUrlPattern } = {}) {
|
|
9
9
|
return async (ctx, next) => {
|
|
10
|
-
if (
|
|
10
|
+
if (ignoreUrlPattern && ctx.req.url.match(ignoreUrlPattern)) {
|
|
11
11
|
return next()
|
|
12
12
|
}
|
|
13
13
|
// request
|
|
@@ -72,7 +72,7 @@ function logResponse({ ctx, start, length, err }) {
|
|
|
72
72
|
const logger = ctx.logger?.child({ name: 'http' })
|
|
73
73
|
const level = err ? 'warn' : 'info'
|
|
74
74
|
if (logger?.isLevelEnabled(level)) {
|
|
75
|
-
|
|
75
|
+
// Get the status code of the response
|
|
76
76
|
const status = err
|
|
77
77
|
? err.status || 500
|
|
78
78
|
: ctx.status || 404
|
package/src/mixins/AssetMixin.js
CHANGED
|
@@ -2,73 +2,77 @@ import { mixin } from '@ditojs/utils'
|
|
|
2
2
|
import { TimeStampedMixin } from './TimeStampedMixin.js'
|
|
3
3
|
|
|
4
4
|
// Asset models are always to be time-stamped:
|
|
5
|
-
export const AssetMixin = mixin(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
required: true,
|
|
10
|
-
unique: true,
|
|
11
|
-
index: true
|
|
12
|
-
},
|
|
13
|
-
|
|
14
|
-
file: {
|
|
15
|
-
type: 'object',
|
|
16
|
-
// TODO: Support this on 'object':
|
|
17
|
-
// required: true
|
|
18
|
-
properties: {
|
|
19
|
-
// The unique key within the storage (uuid/v4 + file extension)
|
|
5
|
+
export const AssetMixin = mixin(
|
|
6
|
+
Model =>
|
|
7
|
+
class extends TimeStampedMixin(Model) {
|
|
8
|
+
static properties = {
|
|
20
9
|
key: {
|
|
21
10
|
type: 'string',
|
|
22
|
-
required: true
|
|
11
|
+
required: true,
|
|
12
|
+
unique: true,
|
|
13
|
+
index: true
|
|
23
14
|
},
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
type: '
|
|
27
|
-
|
|
15
|
+
|
|
16
|
+
file: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
// TODO: Support this on 'object':
|
|
19
|
+
// required: true
|
|
20
|
+
properties: {
|
|
21
|
+
// The unique key within the storage (uuid/v4 + file extension)
|
|
22
|
+
key: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
required: true
|
|
25
|
+
},
|
|
26
|
+
// The original filename, and display name when file is shown
|
|
27
|
+
name: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
required: true
|
|
30
|
+
},
|
|
31
|
+
// The file's mime-type
|
|
32
|
+
type: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
required: true
|
|
35
|
+
},
|
|
36
|
+
// The amount of bytes consumed by the file
|
|
37
|
+
size: {
|
|
38
|
+
type: 'integer',
|
|
39
|
+
required: true
|
|
40
|
+
},
|
|
41
|
+
// Use for storages configured for files to be publicly accessible:
|
|
42
|
+
url: {
|
|
43
|
+
type: 'string'
|
|
44
|
+
},
|
|
45
|
+
// These are only used when the storage defines
|
|
46
|
+
// `config.readDimensions`:
|
|
47
|
+
width: {
|
|
48
|
+
type: 'integer'
|
|
49
|
+
},
|
|
50
|
+
height: {
|
|
51
|
+
type: 'integer'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
28
54
|
},
|
|
29
|
-
|
|
30
|
-
|
|
55
|
+
|
|
56
|
+
storage: {
|
|
31
57
|
type: 'string',
|
|
32
58
|
required: true
|
|
33
59
|
},
|
|
34
|
-
|
|
35
|
-
|
|
60
|
+
|
|
61
|
+
count: {
|
|
36
62
|
type: 'integer',
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// Use for storages configured for files to be publicly accessible:
|
|
40
|
-
url: {
|
|
41
|
-
type: 'string'
|
|
42
|
-
},
|
|
43
|
-
// These are only used when the storage defines `config.readDimensions`:
|
|
44
|
-
width: {
|
|
45
|
-
type: 'integer'
|
|
46
|
-
},
|
|
47
|
-
height: {
|
|
48
|
-
type: 'integer'
|
|
63
|
+
unsigned: true,
|
|
64
|
+
default: 0
|
|
49
65
|
}
|
|
50
66
|
}
|
|
51
|
-
},
|
|
52
|
-
|
|
53
|
-
storage: {
|
|
54
|
-
type: 'string',
|
|
55
|
-
required: true
|
|
56
|
-
},
|
|
57
67
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const { file, storage } = json
|
|
68
|
-
// Convert `AssetMixin#file` to an `AssetFile` instance:
|
|
69
|
-
if (file && storage) {
|
|
70
|
-
this.constructor.app.getStorage(storage)?.convertAssetFile(file)
|
|
68
|
+
// @override
|
|
69
|
+
$parseJson(json) {
|
|
70
|
+
const { file, storage } = json
|
|
71
|
+
// Convert `AssetMixin#file` to an `AssetFile` instance:
|
|
72
|
+
if (file && storage) {
|
|
73
|
+
this.constructor.app.getStorage(storage)?.convertAssetFile(file)
|
|
74
|
+
}
|
|
75
|
+
return json
|
|
76
|
+
}
|
|
71
77
|
}
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
})
|
|
78
|
+
)
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { mixin } from '@ditojs/utils'
|
|
2
2
|
|
|
3
|
-
export const SessionMixin = mixin(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
export const SessionMixin = mixin(
|
|
4
|
+
Model =>
|
|
5
|
+
class extends Model {
|
|
6
|
+
static properties = {
|
|
7
|
+
id: {
|
|
8
|
+
type: 'string',
|
|
9
|
+
primary: true
|
|
10
|
+
},
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
value: {
|
|
13
|
+
type: 'object'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
12
16
|
}
|
|
13
|
-
|
|
14
|
-
})
|
|
17
|
+
)
|
|
@@ -1,37 +1,41 @@
|
|
|
1
1
|
import { mixin } from '@ditojs/utils'
|
|
2
2
|
|
|
3
|
-
export const TimeStampedMixin = mixin(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
export const TimeStampedMixin = mixin(
|
|
4
|
+
Model =>
|
|
5
|
+
class extends Model {
|
|
6
|
+
static properties = {
|
|
7
|
+
createdAt: {
|
|
8
|
+
type: 'timestamp',
|
|
9
|
+
default: 'now()'
|
|
10
|
+
},
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
static scopes = {
|
|
17
|
-
timeStamped: query => query
|
|
18
|
-
.select('createdAt', 'updatedAt')
|
|
19
|
-
}
|
|
12
|
+
updatedAt: {
|
|
13
|
+
type: 'timestamp',
|
|
14
|
+
default: 'now()'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
item.createdAt = now
|
|
26
|
-
item.updatedAt = now
|
|
18
|
+
static scopes = {
|
|
19
|
+
timeStamped: query =>
|
|
20
|
+
query
|
|
21
|
+
.select('createdAt', 'updatedAt')
|
|
27
22
|
}
|
|
28
|
-
},
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
static hooks = {
|
|
25
|
+
'before:insert'({ inputItems }) {
|
|
26
|
+
const now = new Date()
|
|
27
|
+
for (const item of inputItems) {
|
|
28
|
+
item.createdAt = now
|
|
29
|
+
item.updatedAt = now
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
'before:update'({ inputItems }) {
|
|
34
|
+
const now = new Date()
|
|
35
|
+
for (const item of inputItems) {
|
|
36
|
+
item.updatedAt = now
|
|
37
|
+
}
|
|
38
|
+
}
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
|
-
|
|
37
|
-
})
|
|
41
|
+
)
|