apiHelpers.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import type { IncomingHttpHeaders } from 'node:http'
  2. import { snakeCase } from 'lodash'
  3. import z from 'zod'
  4. import { IS_PLATFORM } from '@/lib/constants'
  5. /**
  6. * Construct headers for api request.
  7. * For platform, it will include apiKey into the provided headers.
  8. *
  9. * To prevent relay frontend request headers like useragent, referrer... into the middleware requests.
  10. * We will only keep the header keys that are in this list: Accept, Authorization, Content-Type, x-connection-encrypted
  11. */
  12. export function constructHeaders(headers: { [prop: string]: any }) {
  13. if (headers) {
  14. const cleansedHeaders = {
  15. Accept: headers.Accept,
  16. Authorization: headers.Authorization,
  17. cookie: headers.cookie,
  18. 'Content-Type': headers['Content-Type'],
  19. 'x-connection-encrypted': headers['x-connection-encrypted'],
  20. } as any
  21. // clean up key with underfined value
  22. Object.keys(cleansedHeaders).forEach((key) =>
  23. cleansedHeaders[key] === undefined ? delete cleansedHeaders[key] : {}
  24. )
  25. return {
  26. ...cleansedHeaders,
  27. // [Joshen] JFYI both Alaister and I checked on this and realised this might not be used actually
  28. // Could be safe to remove but leaving it here for now
  29. ...(!IS_PLATFORM && { apiKey: `${process.env.BRIVEN_SERVICE_KEY}` }),
  30. }
  31. } else {
  32. return {
  33. 'Content-Type': 'application/json',
  34. Accept: 'application/json',
  35. }
  36. }
  37. }
  38. // Typically for HTTP payloads
  39. // @ts-ignore
  40. export const toSnakeCase = (object) => {
  41. const snakeCaseObject = {}
  42. const snakeCaseArray = []
  43. if (!object) return null
  44. if (Array.isArray(object)) {
  45. for (const item of object) {
  46. if (typeof item === 'object') {
  47. snakeCaseArray.push(toSnakeCase(item))
  48. } else {
  49. snakeCaseArray.push(item)
  50. }
  51. }
  52. return snakeCaseArray
  53. } else if (typeof object === 'object') {
  54. for (const key of Object.keys(object)) {
  55. if (typeof object[key] === 'object') {
  56. // @ts-ignore
  57. snakeCaseObject[snakeCase(key)] = toSnakeCase(object[key])
  58. } else {
  59. // @ts-ignore
  60. snakeCaseObject[snakeCase(key)] = object[key]
  61. }
  62. }
  63. return snakeCaseObject
  64. } else {
  65. return object
  66. }
  67. }
  68. /**
  69. * Converts Node.js `IncomingHttpHeaders` to Fetch API `Headers`.
  70. */
  71. export function fromNodeHeaders(nodeHeaders: IncomingHttpHeaders): Headers {
  72. const headers = new Headers()
  73. for (const [key, value] of Object.entries(nodeHeaders)) {
  74. if (Array.isArray(value)) {
  75. value.forEach((v) => headers.append(key, v))
  76. } else if (value !== undefined) {
  77. headers.append(key, value)
  78. }
  79. }
  80. return headers
  81. }
  82. /**
  83. * Zod transformer to parse boolean values from strings.
  84. *
  85. * Use when accepting a boolean value in a query parameter.
  86. */
  87. export function zBooleanString(errorMsg?: string) {
  88. return z.string().transform((value, ctx) => {
  89. if (value === 'true') {
  90. return true
  91. }
  92. if (value === 'false') {
  93. return false
  94. }
  95. ctx.addIssue({
  96. code: z.ZodIssueCode.custom,
  97. message: errorMsg || 'must be a boolean string',
  98. })
  99. return z.NEVER
  100. })
  101. }
  102. /**
  103. * Transform a comma-separated string into an array of strings.
  104. *
  105. * Use when accepting a list of values in a query parameter.
  106. */
  107. export function commaSeparatedStringIntoArray(value: string): string[] {
  108. return value
  109. .split(',')
  110. .map((v) => v.trim())
  111. .filter(Boolean)
  112. }
  113. export class InternalServerError extends Error {
  114. constructor(
  115. message: string,
  116. public details?: Record<string, unknown>
  117. ) {
  118. super(message)
  119. this.name = 'InternalServerError'
  120. }
  121. }