@ditojs/server 1.4.2 → 1.5.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/server",
3
- "version": "1.4.2",
3
+ "version": "1.5.1",
4
4
  "type": "module",
5
5
  "description": "Dito.js Server – Dito.js is a declarative and modern web framework, based on Objection.js, Koa.js and Vue.js",
6
6
  "repository": "https://github.com/ditojs/dito/tree/master/packages/server",
@@ -21,22 +21,22 @@
21
21
  "node >= 16"
22
22
  ],
23
23
  "dependencies": {
24
- "@ditojs/admin": "^1.4.2",
25
- "@ditojs/router": "^1.4.1",
26
- "@ditojs/utils": "^1.4.1",
27
- "@koa/cors": "^3.2.0",
24
+ "@ditojs/admin": "^1.5.0",
25
+ "@ditojs/build": "^1.5.0",
26
+ "@ditojs/router": "^1.5.0",
27
+ "@ditojs/utils": "^1.5.0",
28
+ "@koa/cors": "^3.3.0",
28
29
  "@koa/multer": "^3.0.0",
29
30
  "@originjs/vite-plugin-commonjs": "^1.0.3",
30
31
  "ajv": "^8.11.0",
31
32
  "ajv-formats": "^2.1.1",
32
- "aws-sdk": "^2.1098.0",
33
+ "aws-sdk": "^2.1106.0",
33
34
  "axios": "^0.26.1",
34
35
  "bcryptjs": "^2.4.3",
35
36
  "bytes": "^3.1.2",
36
37
  "data-uri-to-buffer": "^4.0.0",
37
38
  "eventemitter2": "^6.4.5",
38
39
  "file-type": "^17.1.1",
39
- "find-up": "^6.3.0",
40
40
  "fs-extra": "^10.0.1",
41
41
  "image-size": "^1.0.1",
42
42
  "is-svg": "^4.3.2",
@@ -55,31 +55,31 @@
55
55
  "mime-types": "^2.1.35",
56
56
  "multer": "^1.4.4",
57
57
  "multer-s3": "^2.10.0",
58
- "nanoid": "^3.3.1",
58
+ "nanoid": "^3.3.2",
59
59
  "parse-duration": "^1.0.2",
60
60
  "passport-local": "^1.0.0",
61
61
  "passthrough-counter": "^1.0.0",
62
62
  "picocolors": "^1.0.0",
63
63
  "picomatch": "^2.3.1",
64
64
  "pino": "^7.9.2",
65
- "pino-pretty": "^7.5.4",
65
+ "pino-pretty": "^7.6.0",
66
66
  "pluralize": "^8.0.0",
67
67
  "repl": "^0.1.3",
68
68
  "uuid": "^8.3.2",
69
- "vite": "^2.8.6",
69
+ "vite": "^2.9.1",
70
70
  "vite-plugin-vue2": "^1.9.3",
71
71
  "vue": "^2.6.14",
72
72
  "vue-template-compiler": "^2.6.14"
73
73
  },
74
74
  "peerDependencies": {
75
- "knex": "^0.21.0",
76
- "objection": "^2.2.0"
75
+ "knex": "^1.0.4",
76
+ "objection": "^3.0.1"
77
77
  },
78
78
  "devDependencies": {
79
- "knex": "^0.21.21",
80
- "objection": "^2.2.18",
79
+ "knex": "^1.0.4",
80
+ "objection": "^3.0.1",
81
81
  "pg": "^8.7.3",
82
82
  "sqlite3": "^5.0.2"
83
83
  },
84
- "gitHead": "1790e50f979322b47a53421e9facddb86f6eff86"
84
+ "gitHead": "40731960cf8b528f4f40f8b7e43e2e966138337c"
85
85
  }
@@ -1,11 +1,13 @@
1
+ import os from 'os'
2
+ import path from 'path'
3
+ import util from 'util'
4
+ import zlib from 'zlib'
5
+ import fs from 'fs-extra'
1
6
  import Koa from 'koa'
2
7
  import Knex from 'knex'
3
- import util from 'util'
4
8
  import axios from 'axios'
5
9
  import pico from 'picocolors'
6
- import zlib from 'zlib'
7
10
  import pino from 'pino'
8
- import os from 'os'
9
11
  import parseDuration from 'parse-duration'
10
12
  import bodyParser from 'koa-bodyparser'
11
13
  import cors from '@koa/cors'
@@ -46,11 +48,10 @@ import {
46
48
  handleUser,
47
49
  logRequests
48
50
  } from '../middleware/index.js'
49
- import objection, {
51
+ import {
50
52
  Model,
51
53
  BelongsToOneRelation,
52
- // TODO: Import directly once we can move to Objection 3
53
- // knexSnakeCaseMappers,
54
+ knexSnakeCaseMappers,
54
55
  ref
55
56
  } from 'objection'
56
57
 
@@ -626,7 +627,7 @@ export class Application extends Koa {
626
627
  if (snakeCaseOptions) {
627
628
  knex = {
628
629
  ...knex,
629
- ...objection.knexSnakeCaseMappers(snakeCaseOptions)
630
+ ...knexSnakeCaseMappers(snakeCaseOptions)
630
631
  }
631
632
  }
632
633
  this.knex = Knex(knex)
@@ -853,23 +854,38 @@ export class Application extends Koa {
853
854
  if (file.data || file.url) {
854
855
  let { data } = file
855
856
  if (!data) {
857
+ const { url } = file
858
+ if (!storage.isImportSourceAllowed(url)) {
859
+ throw new AssetError(
860
+ `Unable to import asset from foreign source: '${
861
+ file.name
862
+ }' ('${
863
+ url
864
+ }'): The source needs to be explicitly allowed.`
865
+ )
866
+ }
856
867
  console.info(
857
868
  `${
858
869
  pico.red('INFO:')
859
870
  } Asset ${
860
871
  pico.green(`'${file.name}'`)
861
872
  } is from a foreign source, fetching from ${
862
- pico.green(`'${file.url}'`)
873
+ pico.green(`'${url}'`)
863
874
  } and adding to storage ${
864
875
  pico.green(`'${storage.name}'`)
865
876
  }...`
866
877
  )
867
- const response = await axios.request({
868
- method: 'get',
869
- url: file.url,
870
- responseType: 'arraybuffer'
871
- })
872
- data = response.data
878
+ if (url.startsWith('file://')) {
879
+ const filepath = path.resolve(url.substring(7))
880
+ data = await fs.readFile(filepath)
881
+ } else {
882
+ const response = await axios.request({
883
+ method: 'get',
884
+ responseType: 'arraybuffer',
885
+ url
886
+ })
887
+ data = response.data
888
+ }
873
889
  }
874
890
  const importedFile = await storage.addFile(file, data)
875
891
  await this.createAssets(storage, [importedFile], 0, trx)
@@ -1,5 +1,4 @@
1
1
  import path from 'path'
2
- import fs from 'fs'
3
2
  import Koa from 'koa'
4
3
  import serve from 'koa-static'
5
4
  import { defineConfig, createServer } from 'vite'
@@ -7,8 +6,10 @@ import { createVuePlugin } from 'vite-plugin-vue2'
7
6
  import {
8
7
  viteCommonjs as createCommonJsPlugin
9
8
  } from '@originjs/vite-plugin-commonjs'
10
- import picomatch from 'picomatch'
11
- import { findUpSync } from 'find-up'
9
+ import {
10
+ createRollupImportsResolver,
11
+ testModuleIdentifier
12
+ } from '@ditojs/build'
12
13
  import { merge } from '@ditojs/utils'
13
14
  import { Controller } from './Controller.js'
14
15
  import { handleConnectMiddleware } from '../middleware/index.js'
@@ -135,16 +136,11 @@ export class AdminController extends Controller {
135
136
  getViteConfig(config = {}) {
136
137
  const development = this.mode === 'development'
137
138
 
138
- const cwd = path.resolve('.')
139
+ const cwd = process.cwd()
139
140
  const root = this.getPath('root')
140
141
  const base = `${this.url}/`
141
142
  const views = path.join(root, 'views')
142
143
 
143
- // Read `package.json` from the closest package.json, so we can emulate
144
- // ESM-style imports mappings in rollup / vite.
145
- const pkg = findUpSync('package.json', { cwd: root })
146
- const { imports = {} } = JSON.parse(fs.readFileSync(pkg, 'utf8'))
147
-
148
144
  return defineConfig(merge({
149
145
  root,
150
146
  base,
@@ -185,7 +181,7 @@ export class AdminController extends Controller {
185
181
  return 'common'
186
182
  } else {
187
183
  const module = id.match(/node_modules\/([^/$]*)/)?.[1] || ''
188
- return picomatch.isMatch(module, CORE_DEPENDENCIES)
184
+ return testModuleIdentifier(module, CORE_DEPENDENCIES)
189
185
  ? 'core'
190
186
  : 'vendor'
191
187
  }
@@ -210,27 +206,7 @@ export class AdminController extends Controller {
210
206
  find: '@',
211
207
  replacement: root
212
208
  },
213
- {
214
- // Use a custom rollup resolver to emulate ESM-style imports
215
- // mappings in vite, as read from `package.json` above:
216
- find: /^#/,
217
- replacement: '#',
218
- customResolver(id) {
219
- for (const [find, replacement] of Object.entries(imports)) {
220
- picomatch.isMatch(id, find.replace('*', '**'), {
221
- capture: true,
222
- onMatch({ input, regex }) {
223
- const replacementPath = path.resolve(replacement)
224
- const match = input.match(regex)?.[1]
225
- id = match
226
- ? replacementPath.replace('*', match)
227
- : replacementPath
228
- }
229
- })
230
- }
231
- return id
232
- }
233
- }
209
+ createRollupImportsResolver({ cwd: root })
234
210
  ]
235
211
  }
236
212
  }, config))
@@ -68,17 +68,18 @@ export class CollectionController extends Controller {
68
68
  return this.extendContext(ctx, { memberId })
69
69
  }
70
70
 
71
- getCollectionIds(ctx) {
71
+ getModelId(model) {
72
72
  const idProperty = this.modelClass.getIdProperty()
73
73
  // Handle both composite keys and normal ones.
74
- const getId = isArray(idProperty)
75
- ? model => idProperty.reduce(
76
- (id, key) => {
77
- id.push(model[key])
78
- return id
79
- }, [])
80
- : model => model[idProperty]
81
- return asArray(ctx.request.body).map(model => this.validateId(getId(model)))
74
+ return isArray(idProperty)
75
+ ? idProperty.map(property => model[property])
76
+ : model[idProperty]
77
+ }
78
+
79
+ getCollectionIds(ctx) {
80
+ return asArray(ctx.request.body).map(
81
+ model => this.validateId(this.getModelId(model))
82
+ )
82
83
  }
83
84
 
84
85
  getIds(ctx) {
@@ -219,9 +220,9 @@ export class CollectionController extends Controller {
219
220
  .modify(getModify(modify, trx))
220
221
  )
221
222
  : await this.executeAndFetch('insert', ctx, modify)
222
- ctx.status = 201
223
+ ctx.status = 201 // Created
223
224
  if (isObject(result)) {
224
- ctx.set('Location', this.getUrl('collection', result.id))
225
+ ctx.set('Location', this.getUrl('collection', this.getModelId(result)))
225
226
  }
226
227
  return result
227
228
  },
@@ -17,6 +17,10 @@ export function handleUser() {
17
17
  const { user } = ctx.state
18
18
  await user?.$emit('before:logout', options)
19
19
  await logout.call(this) // No options in passport's logout()
20
+ // Clear the session after logout, apparently koa-passport doesn't take
21
+ // care of this itself:
22
+ // https://stackoverflow.com/questions/55818887/koa-passport-logout-is-not-clearing-session
23
+ ctx.session = null
20
24
  await user?.$emit('after:logout', options)
21
25
  }
22
26
 
@@ -929,11 +929,9 @@ export class Model extends objection.Model {
929
929
  )
930
930
  : assetDataPaths
931
931
 
932
- // `dataPaths` will be empty in the case of an update/insert that do not
932
+ // `dataPaths` is empty in the case of an update/insert that does not
933
933
  // affect the assets.
934
- if (dataPaths.length === 0) {
935
- return
936
- }
934
+ if (dataPaths.length === 0) return
937
935
 
938
936
  // Load the model's asset files in their current state before the query is
939
937
  // executed.
@@ -972,7 +970,7 @@ export class Model extends objection.Model {
972
970
  if (modifiedFiles.length > 0) {
973
971
  // TODO: `modifiedFiles` should be restored as well, but that's far
974
972
  // from trivial since no backup is kept in `handleModifiedAssets`
975
- console.info(
973
+ console.warn(
976
974
  `Unable to restore these already modified files: ${
977
975
  modifiedFiles.map(file => `'${file.name}'`)
978
976
  }`
@@ -216,24 +216,19 @@ export class QueryBuilder extends objection.QueryBuilder {
216
216
  this._allowScopes = query._allowScopes ? { ...query._allowScopes } : null
217
217
  this._ignoreScopes = { ...query._ignoreScopes }
218
218
  }
219
+ const scopes = isChildQuery
220
+ // When copying scopes for child-queries, we also need to take the already
221
+ // applied scopes into account and copy those too.
222
+ ? { ...query._appliedScopes, ...query._scopes }
223
+ : { ...query._scopes }
219
224
  // If the target is a child query of a graph query, copy all scopes, graph
220
225
  // and non-graph. If it is a child query of a related or eager query,
221
226
  // copy only the graph scopes.
222
227
  const copyAllScopes =
223
228
  isSameModelClass && isChildQuery && query.has(/GraphAndFetch$/)
224
- // When copying scopes for child-queries, we also need to take the already
225
- // applied scopes into account and copy those too.
226
- const scopes = isChildQuery
227
- ? { ...query._appliedScopes, ...query._scopes }
228
- : query._scopes
229
- this._scopes = this._filterScopes(scopes, (scope, graph) =>
230
- copyAllScopes || graph)
231
- }
232
-
233
- _filterScopes(scopes, callback) {
234
- return Object.fromEntries(Object.entries(scopes).filter(
235
- ([scope, graph]) => callback(scope, graph)
236
- ))
229
+ this._scopes = copyAllScopes
230
+ ? scopes
231
+ : filterScopes(scopes, (scope, graph) => graph) // copy graph-scopes only.
237
232
  }
238
233
 
239
234
  _applyScope(scope, graph) {
@@ -797,6 +792,12 @@ for (const key of [
797
792
  }
798
793
  }
799
794
 
795
+ function filterScopes(scopes, callback) {
796
+ return Object.fromEntries(Object.entries(scopes).filter(
797
+ ([scope, graph]) => callback(scope, graph)
798
+ ))
799
+ }
800
+
800
801
  // The default options for insertDitoGraph(), upsertDitoGraph(),
801
802
  // updateDitoGraph() and patchDitoGraph()
802
803
  const insertDitoGraphOptions = {
@@ -5,7 +5,7 @@ import {
5
5
  HasManyRelation,
6
6
  ManyToManyRelation
7
7
  } from 'objection'
8
- import { Model } from '../models.js'
8
+ import { Model } from '../models/index.js'
9
9
  import {
10
10
  getRelationClass, convertRelation, addRelationSchemas
11
11
  } from './relations.js'
@@ -1,8 +1,9 @@
1
1
  import path from 'path'
2
+ import { URL } from 'url'
2
3
  import multer from '@koa/multer'
4
+ import picomatch from 'picomatch'
3
5
  import imageSize from 'image-size'
4
6
  import { PassThrough } from 'stream'
5
- import { URL } from 'url'
6
7
  import { hyphenate, toPromiseCallback } from '@ditojs/utils'
7
8
  import { AssetFile } from './AssetFile.js'
8
9
 
@@ -57,6 +58,10 @@ export class Storage {
57
58
  return AssetFile.getUniqueKey(name)
58
59
  }
59
60
 
61
+ isImportSourceAllowed(url) {
62
+ return picomatch.isMatch(url, this.config.allowedImports || [])
63
+ }
64
+
60
65
  convertAssetFile(file) {
61
66
  return AssetFile.convert(file, this)
62
67
  }