@budibase/backend-core 2.8.21 → 2.8.22-alpha.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.
Files changed (38) hide show
  1. package/dist/package.json +4 -3
  2. package/dist/src/auth/auth.js.map +1 -1
  3. package/dist/src/cache/appMetadata.d.ts +7 -1
  4. package/dist/src/cache/appMetadata.js +5 -8
  5. package/dist/src/cache/appMetadata.js.map +1 -1
  6. package/dist/src/cache/user.js.map +1 -1
  7. package/dist/src/constants/misc.d.ts +2 -0
  8. package/dist/src/constants/misc.js +2 -0
  9. package/dist/src/constants/misc.js.map +1 -1
  10. package/dist/src/db/searchIndexes/searchIndexes.js.map +1 -1
  11. package/dist/src/db/utils.js +7 -2
  12. package/dist/src/db/utils.js.map +1 -1
  13. package/dist/src/environment.d.ts +5 -3
  14. package/dist/src/environment.js +13 -70
  15. package/dist/src/environment.js.map +1 -1
  16. package/dist/src/logging/index.d.ts +1 -1
  17. package/dist/src/logging/index.js +2 -3
  18. package/dist/src/logging/index.js.map +1 -1
  19. package/dist/src/logging/pino/logger.js +40 -24
  20. package/dist/src/logging/pino/logger.js.map +1 -1
  21. package/dist/src/logging/system.d.ts +9 -0
  22. package/dist/src/logging/system.js +101 -0
  23. package/dist/src/logging/system.js.map +1 -0
  24. package/dist/src/users.js.map +1 -1
  25. package/dist/tsconfig.build.tsbuildinfo +1 -1
  26. package/package.json +4 -3
  27. package/src/auth/auth.ts +1 -1
  28. package/src/cache/appMetadata.ts +10 -8
  29. package/src/cache/user.ts +1 -1
  30. package/src/constants/misc.ts +2 -0
  31. package/src/db/searchIndexes/searchIndexes.ts +1 -1
  32. package/src/db/utils.ts +9 -3
  33. package/src/environment.ts +12 -4
  34. package/src/logging/index.ts +1 -3
  35. package/src/logging/pino/logger.ts +45 -24
  36. package/src/logging/system.ts +81 -0
  37. package/src/logging/tests/system.spec.ts +61 -0
  38. package/src/users.ts +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@budibase/backend-core",
3
- "version": "2.8.21",
3
+ "version": "2.8.22-alpha.1",
4
4
  "description": "Budibase backend core libraries used in server and worker",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "@budibase/nano": "10.1.2",
24
24
  "@budibase/pouchdb-replication-stream": "1.2.10",
25
- "@budibase/types": "2.8.21",
25
+ "@budibase/types": "2.8.22-alpha.1",
26
26
  "@shopify/jest-koa-mocks": "5.0.1",
27
27
  "@techpass/passport-openidconnect": "0.3.2",
28
28
  "aws-cloudfront-sign": "2.2.0",
@@ -51,6 +51,7 @@
51
51
  "pouchdb": "7.3.0",
52
52
  "pouchdb-find": "7.2.2",
53
53
  "redlock": "4.2.0",
54
+ "rotating-file-stream": "3.1.0",
54
55
  "sanitize-s3-objectkey": "0.0.1",
55
56
  "semver": "7.3.7",
56
57
  "tar-fs": "2.1.1",
@@ -101,5 +102,5 @@
101
102
  }
102
103
  }
103
104
  },
104
- "gitHead": "26c531ec81a0bdfb5805f2c8a85d6bbcc5ac7d81"
105
+ "gitHead": "efcef8d6f8ce0744d1f80f8161a41f55ef6c8ea4"
105
106
  }
package/src/auth/auth.ts CHANGED
@@ -159,7 +159,7 @@ export async function updateUserOAuth(userId: string, oAuthConfig: any) {
159
159
 
160
160
  try {
161
161
  const db = getGlobalDB()
162
- const dbUser = await db.get(userId)
162
+ const dbUser = await db.get<any>(userId)
163
163
 
164
164
  //Do not overwrite the refresh token if a valid one is not provided.
165
165
  if (typeof details.refreshToken !== "string") {
@@ -2,9 +2,14 @@ import { getAppClient } from "../redis/init"
2
2
  import { doWithDB, DocumentType } from "../db"
3
3
  import { Database, App } from "@budibase/types"
4
4
 
5
- const AppState = {
6
- INVALID: "invalid",
5
+ export enum AppState {
6
+ INVALID = "invalid",
7
7
  }
8
+
9
+ export interface DeletedApp {
10
+ state: AppState
11
+ }
12
+
8
13
  const EXPIRY_SECONDS = 3600
9
14
 
10
15
  /**
@@ -31,7 +36,7 @@ function isInvalid(metadata?: { state: string }) {
31
36
  * @param {string} appId the id of the app to get metadata from.
32
37
  * @returns {object} the app metadata.
33
38
  */
34
- export async function getAppMetadata(appId: string) {
39
+ export async function getAppMetadata(appId: string): Promise<App | DeletedApp> {
35
40
  const client = await getAppClient()
36
41
  // try cache
37
42
  let metadata = await client.get(appId)
@@ -61,11 +66,8 @@ export async function getAppMetadata(appId: string) {
61
66
  }
62
67
  await client.store(appId, metadata, expiry)
63
68
  }
64
- // we've stored in the cache an object to tell us that it is currently invalid
65
- if (isInvalid(metadata)) {
66
- throw { status: 404, message: "No app metadata found" }
67
- }
68
- return metadata as App
69
+
70
+ return metadata
69
71
  }
70
72
 
71
73
  /**
package/src/cache/user.ts CHANGED
@@ -12,7 +12,7 @@ const EXPIRY_SECONDS = 3600
12
12
  */
13
13
  async function populateFromDB(userId: string, tenantId: string) {
14
14
  const db = tenancy.getTenantDB(tenantId)
15
- const user = await db.get(userId)
15
+ const user = await db.get<any>(userId)
16
16
  user.budibaseAccess = true
17
17
  if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
18
18
  const account = await accounts.getAccount(user.email)
@@ -20,6 +20,8 @@ export enum Header {
20
20
  TYPE = "x-budibase-type",
21
21
  PREVIEW_ROLE = "x-budibase-role",
22
22
  TENANT_ID = "x-budibase-tenant-id",
23
+ VERIFICATION_CODE = "x-budibase-verification-code",
24
+ RETURN_VERIFICATION_CODE = "x-budibase-return-verification-code",
23
25
  TOKEN = "x-budibase-token",
24
26
  CSRF_TOKEN = "x-csrf-token",
25
27
  CORRELATION_ID = "x-budibase-correlation-id",
@@ -5,7 +5,7 @@ export async function createUserIndex() {
5
5
  const db = getGlobalDB()
6
6
  let designDoc
7
7
  try {
8
- designDoc = await db.get("_design/database")
8
+ designDoc = await db.get<any>("_design/database")
9
9
  } catch (err: any) {
10
10
  if (err.status === 404) {
11
11
  designDoc = { _id: "_design/database" }
package/src/db/utils.ts CHANGED
@@ -2,7 +2,7 @@ import env from "../environment"
2
2
  import { DEFAULT_TENANT_ID, SEPARATOR, DocumentType } from "../constants"
3
3
  import { getTenantId, getGlobalDBName } from "../context"
4
4
  import { doWithDB, directCouchAllDbs } from "./db"
5
- import { getAppMetadata } from "../cache/appMetadata"
5
+ import { AppState, DeletedApp, getAppMetadata } from "../cache/appMetadata"
6
6
  import { isDevApp, isDevAppID, getProdAppID } from "../docIds/conversions"
7
7
  import { App, Database } from "@budibase/types"
8
8
  import { getStartEndKeyURL } from "../docIds"
@@ -101,7 +101,9 @@ export async function getAllApps({
101
101
  const response = await Promise.allSettled(appPromises)
102
102
  const apps = response
103
103
  .filter(
104
- (result: any) => result.status === "fulfilled" && result.value != null
104
+ (result: any) =>
105
+ result.status === "fulfilled" &&
106
+ result.value?.state !== AppState.INVALID
105
107
  )
106
108
  .map(({ value }: any) => value)
107
109
  if (!all) {
@@ -126,7 +128,11 @@ export async function getAppsByIDs(appIds: string[]) {
126
128
  )
127
129
  // have to list the apps which exist, some may have been deleted
128
130
  return settled
129
- .filter(promise => promise.status === "fulfilled")
131
+ .filter(
132
+ promise =>
133
+ promise.status === "fulfilled" &&
134
+ (promise.value as DeletedApp).state !== AppState.INVALID
135
+ )
130
136
  .map(promise => (promise as PromiseFulfilledResult<App>).value)
131
137
  }
132
138
 
@@ -47,7 +47,10 @@ function httpLogging() {
47
47
  return process.env.HTTP_LOGGING
48
48
  }
49
49
 
50
- function findVersion() {
50
+ function getPackageJsonFields(): {
51
+ VERSION: string
52
+ SERVICE_NAME: string
53
+ } {
51
54
  function findFileInAncestors(
52
55
  fileName: string,
53
56
  currentDir: string
@@ -69,10 +72,14 @@ function findVersion() {
69
72
  try {
70
73
  const packageJsonFile = findFileInAncestors("package.json", process.cwd())
71
74
  const content = readFileSync(packageJsonFile!, "utf-8")
72
- return JSON.parse(content).version
75
+ const parsedContent = JSON.parse(content)
76
+ return {
77
+ VERSION: parsedContent.version,
78
+ SERVICE_NAME: parsedContent.name,
79
+ }
73
80
  } catch {
74
81
  // throwing an error here is confusing/causes backend-core to be hard to import
75
- return undefined
82
+ return { VERSION: "", SERVICE_NAME: "" }
76
83
  }
77
84
  }
78
85
 
@@ -154,13 +161,14 @@ const environment = {
154
161
  ENABLE_SSO_MAINTENANCE_MODE: selfHosted
155
162
  ? process.env.ENABLE_SSO_MAINTENANCE_MODE
156
163
  : false,
157
- VERSION: findVersion(),
164
+ ...getPackageJsonFields(),
158
165
  DISABLE_PINO_LOGGER: process.env.DISABLE_PINO_LOGGER,
159
166
  _set(key: any, value: any) {
160
167
  process.env[key] = value
161
168
  // @ts-ignore
162
169
  environment[key] = value
163
170
  },
171
+ ROLLING_LOG_MAX_SIZE: process.env.ROLLING_LOG_MAX_SIZE || "10M",
164
172
  }
165
173
 
166
174
  // clean up any environment variable edge cases
@@ -1,6 +1,4 @@
1
1
  export * as correlation from "./correlation/correlation"
2
2
  export { logger } from "./pino/logger"
3
3
  export * from "./alerts"
4
-
5
- // turn off or on context logging i.e. tenantId, appId etc
6
- export let LOG_CONTEXT = true
4
+ export * as system from "./system"
@@ -1,37 +1,60 @@
1
- import env from "../../environment"
2
1
  import pino, { LoggerOptions } from "pino"
2
+ import pinoPretty from "pino-pretty"
3
+
4
+ import { IdentityType } from "@budibase/types"
5
+ import env from "../../environment"
3
6
  import * as context from "../../context"
4
7
  import * as correlation from "../correlation"
5
- import { IdentityType } from "@budibase/types"
6
- import { LOG_CONTEXT } from "../index"
8
+
9
+ import { localFileDestination } from "../system"
7
10
 
8
11
  // LOGGER
9
12
 
10
13
  let pinoInstance: pino.Logger | undefined
11
14
  if (!env.DISABLE_PINO_LOGGER) {
15
+ const level = env.LOG_LEVEL
12
16
  const pinoOptions: LoggerOptions = {
13
- level: env.LOG_LEVEL,
17
+ level,
14
18
  formatters: {
15
- level: label => {
16
- return { level: label.toUpperCase() }
19
+ level: level => {
20
+ return { level: level.toUpperCase() }
17
21
  },
18
22
  bindings: () => {
19
- return {}
23
+ if (env.SELF_HOSTED) {
24
+ // "service" is being injected in datadog using the pod names,
25
+ // so we should leave it blank to allow the default behaviour if it's not running self-hosted
26
+ return {
27
+ service: env.SERVICE_NAME,
28
+ }
29
+ } else {
30
+ return {}
31
+ }
20
32
  },
21
33
  },
22
34
  timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`,
23
35
  }
24
36
 
25
- if (env.isDev()) {
26
- pinoOptions.transport = {
27
- target: "pino-pretty",
28
- options: {
29
- singleLine: true,
30
- },
31
- }
37
+ const destinations: pino.StreamEntry[] = []
38
+
39
+ destinations.push(
40
+ env.isDev()
41
+ ? {
42
+ stream: pinoPretty({ singleLine: true }),
43
+ level: level as pino.Level,
44
+ }
45
+ : { stream: process.stdout, level: level as pino.Level }
46
+ )
47
+
48
+ if (env.SELF_HOSTED) {
49
+ destinations.push({
50
+ stream: localFileDestination(),
51
+ level: level as pino.Level,
52
+ })
32
53
  }
33
54
 
34
- pinoInstance = pino(pinoOptions)
55
+ pinoInstance = destinations.length
56
+ ? pino(pinoOptions, pino.multistream(destinations))
57
+ : pino(pinoOptions)
35
58
 
36
59
  // CONSOLE OVERRIDES
37
60
 
@@ -83,15 +106,13 @@ if (!env.DISABLE_PINO_LOGGER) {
83
106
 
84
107
  let contextObject = {}
85
108
 
86
- if (LOG_CONTEXT) {
87
- contextObject = {
88
- tenantId: getTenantId(),
89
- appId: getAppId(),
90
- automationId: getAutomationId(),
91
- identityId: identity?._id,
92
- identityType: identity?.type,
93
- correlationId: correlation.getId(),
94
- }
109
+ contextObject = {
110
+ tenantId: getTenantId(),
111
+ appId: getAppId(),
112
+ automationId: getAutomationId(),
113
+ identityId: identity?._id,
114
+ identityType: identity?.type,
115
+ correlationId: correlation.getId(),
95
116
  }
96
117
 
97
118
  const mergingObject: any = {
@@ -0,0 +1,81 @@
1
+ import fs from "fs"
2
+ import path from "path"
3
+ import * as rfs from "rotating-file-stream"
4
+
5
+ import env from "../environment"
6
+ import { budibaseTempDir } from "../objectStore"
7
+
8
+ const logsFileName = `budibase.log`
9
+ const budibaseLogsHistoryFileName = "budibase-logs-history.txt"
10
+
11
+ const logsPath = path.join(budibaseTempDir(), "systemlogs")
12
+
13
+ function getFullPath(fileName: string) {
14
+ return path.join(logsPath, fileName)
15
+ }
16
+
17
+ export function getSingleFileMaxSizeInfo(totalMaxSize: string) {
18
+ const regex = /(\d+)([A-Za-z])/
19
+ const match = totalMaxSize?.match(regex)
20
+ if (!match) {
21
+ console.warn(`totalMaxSize does not have a valid value`, {
22
+ totalMaxSize,
23
+ })
24
+ return undefined
25
+ }
26
+
27
+ const size = +match[1]
28
+ const unit = match[2]
29
+ if (size === 1) {
30
+ switch (unit) {
31
+ case "B":
32
+ return { size: `${size}B`, totalHistoryFiles: 1 }
33
+ case "K":
34
+ return { size: `${(size * 1000) / 2}B`, totalHistoryFiles: 1 }
35
+ case "M":
36
+ return { size: `${(size * 1000) / 2}K`, totalHistoryFiles: 1 }
37
+ case "G":
38
+ return { size: `${(size * 1000) / 2}M`, totalHistoryFiles: 1 }
39
+ default:
40
+ return undefined
41
+ }
42
+ }
43
+
44
+ if (size % 2 === 0) {
45
+ return { size: `${size / 2}${unit}`, totalHistoryFiles: 1 }
46
+ }
47
+
48
+ return { size: `1${unit}`, totalHistoryFiles: size - 1 }
49
+ }
50
+
51
+ export function localFileDestination() {
52
+ const fileInfo = getSingleFileMaxSizeInfo(env.ROLLING_LOG_MAX_SIZE)
53
+ const outFile = rfs.createStream(logsFileName, {
54
+ // As we have a rolling size, we want to half the max size
55
+ size: fileInfo?.size,
56
+ path: logsPath,
57
+ maxFiles: fileInfo?.totalHistoryFiles || 1,
58
+ immutable: true,
59
+ history: budibaseLogsHistoryFileName,
60
+ initialRotation: false,
61
+ })
62
+
63
+ return outFile
64
+ }
65
+
66
+ export function getLogReadStream() {
67
+ const streams = []
68
+ const historyFile = getFullPath(budibaseLogsHistoryFileName)
69
+ if (fs.existsSync(historyFile)) {
70
+ const fileContent = fs.readFileSync(historyFile, "utf-8")
71
+ const historyFiles = fileContent.split("\n")
72
+ for (const historyFile of historyFiles.filter(x => x)) {
73
+ streams.push(fs.readFileSync(historyFile))
74
+ }
75
+ }
76
+
77
+ streams.push(fs.readFileSync(getFullPath(logsFileName)))
78
+
79
+ const combinedContent = Buffer.concat(streams)
80
+ return combinedContent
81
+ }
@@ -0,0 +1,61 @@
1
+ import { getSingleFileMaxSizeInfo } from "../system"
2
+
3
+ describe("system", () => {
4
+ describe("getSingleFileMaxSizeInfo", () => {
5
+ it.each([
6
+ ["100B", "50B"],
7
+ ["200K", "100K"],
8
+ ["20M", "10M"],
9
+ ["4G", "2G"],
10
+ ])(
11
+ "Halving even number (%s) returns halved size and 1 history file (%s)",
12
+ (totalValue, expectedMaxSize) => {
13
+ const result = getSingleFileMaxSizeInfo(totalValue)
14
+ expect(result).toEqual({
15
+ size: expectedMaxSize,
16
+ totalHistoryFiles: 1,
17
+ })
18
+ }
19
+ )
20
+
21
+ it.each([
22
+ ["5B", "1B", 4],
23
+ ["17K", "1K", 16],
24
+ ["21M", "1M", 20],
25
+ ["3G", "1G", 2],
26
+ ])(
27
+ "Halving an odd number (%s) returns as many files as size (-1) (%s)",
28
+ (totalValue, expectedMaxSize, totalHistoryFiles) => {
29
+ const result = getSingleFileMaxSizeInfo(totalValue)
30
+ expect(result).toEqual({
31
+ size: expectedMaxSize,
32
+ totalHistoryFiles,
33
+ })
34
+ }
35
+ )
36
+
37
+ it.each([
38
+ ["1B", "1B"],
39
+ ["1K", "500B"],
40
+ ["1M", "500K"],
41
+ ["1G", "500M"],
42
+ ])(
43
+ "Halving '%s' returns halved unit (%s)",
44
+ (totalValue, expectedMaxSize) => {
45
+ const result = getSingleFileMaxSizeInfo(totalValue)
46
+ expect(result).toEqual({
47
+ size: expectedMaxSize,
48
+ totalHistoryFiles: 1,
49
+ })
50
+ }
51
+ )
52
+
53
+ it.each([[undefined], [""], ["50"], ["wrongvalue"]])(
54
+ "Halving wrongly formatted value ('%s') returns undefined",
55
+ totalValue => {
56
+ const result = getSingleFileMaxSizeInfo(totalValue!)
57
+ expect(result).toBeUndefined()
58
+ }
59
+ )
60
+ })
61
+ })
package/src/users.ts CHANGED
@@ -67,9 +67,9 @@ export const bulkUpdateGlobalUsers = async (users: User[]) => {
67
67
 
68
68
  export async function getById(id: string, opts?: GetOpts): Promise<User> {
69
69
  const db = context.getGlobalDB()
70
- let user = await db.get(id)
70
+ let user = await db.get<User>(id)
71
71
  if (opts?.cleanup) {
72
- user = removeUserPassword(user)
72
+ user = removeUserPassword(user) as User
73
73
  }
74
74
  return user
75
75
  }