@ditojs/server 1.4.3 → 1.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ditojs/server",
3
- "version": "1.4.3",
3
+ "version": "1.5.0",
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.3",
25
- "@ditojs/router": "^1.4.3",
26
- "@ditojs/utils": "^1.4.3",
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,30 @@
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
- "picomatch": "^2.3.1",
64
63
  "pino": "^7.9.2",
65
- "pino-pretty": "^7.5.4",
64
+ "pino-pretty": "^7.6.0",
66
65
  "pluralize": "^8.0.0",
67
66
  "repl": "^0.1.3",
68
67
  "uuid": "^8.3.2",
69
- "vite": "^2.8.6",
68
+ "vite": "^2.9.1",
70
69
  "vite-plugin-vue2": "^1.9.3",
71
70
  "vue": "^2.6.14",
72
71
  "vue-template-compiler": "^2.6.14"
73
72
  },
74
73
  "peerDependencies": {
75
- "knex": "^0.21.0",
76
- "objection": "^2.2.0"
74
+ "knex": "^1.0.4",
75
+ "objection": "^3.0.1"
77
76
  },
78
77
  "devDependencies": {
79
- "knex": "^0.21.21",
80
- "objection": "^2.2.18",
78
+ "knex": "^1.0.4",
79
+ "objection": "^3.0.1",
81
80
  "pg": "^8.7.3",
82
81
  "sqlite3": "^5.0.2"
83
82
  },
84
- "gitHead": "f2a544aef8ab0a12c7019650cd86558f8365b007"
83
+ "gitHead": "36a57512228cc233211bf6e4038123a9e775f5c5"
85
84
  }
@@ -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
  },
@@ -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
  }`
@@ -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'
@@ -5,6 +5,7 @@ import { PassThrough } from 'stream'
5
5
  import { URL } from 'url'
6
6
  import { hyphenate, toPromiseCallback } from '@ditojs/utils'
7
7
  import { AssetFile } from './AssetFile.js'
8
+ import { matchGlobPattern } from '../utils/glob.js'
8
9
 
9
10
  const storageClasses = {}
10
11
 
@@ -57,6 +58,14 @@ export class Storage {
57
58
  return AssetFile.getUniqueKey(name)
58
59
  }
59
60
 
61
+ isImportSourceAllowed(url) {
62
+ const { allowedImports = [] } = this.config
63
+ for (const pattern of allowedImports) {
64
+ if (matchGlobPattern(url, pattern)) return true
65
+ }
66
+ return false
67
+ }
68
+
60
69
  convertAssetFile(file) {
61
70
  return AssetFile.convert(file, this)
62
71
  }
@@ -0,0 +1,9 @@
1
+ import { escapeRegexp } from '@ditojs/utils'
2
+
3
+ export function matchGlobPattern(str, pattern) {
4
+ const exp = escapeRegexp(pattern).replace(
5
+ /\\([*?])/,
6
+ (_, chr) => chr === '*' ? '.*' : chr
7
+ )
8
+ return new RegExp(`^${exp}$`).test(str)
9
+ }