import type { DMMF } from '@prisma/generator-helper'

import { getGroupByArgsName, getModelArgName } from '../utils'
import { capitalize, lowerCase } from '../utils/common'

export interface JSDocMethodBodyCtx {
  singular: string
  plural: string
  firstScalar: DMMF.Field | undefined
  method: string
  model: DMMF.Model
  action: DMMF.ModelAction
  mapping: DMMF.ModelMapping
}

const Docs = {
  cursor: `{@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs}`,
  pagination: `{@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs}`,
  aggregations: `{@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs}`,
  distinct: `{@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs}`,
  sorting: `{@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs}`,
}

type JSDocsType = {
  [action in DMMF.ModelAction]: {
    body: (ctx: JSDocMethodBodyCtx) => string
    fields: {
      [field: string]: (singular: string, plural: string) => string
    }
  }
}

function addLinkToDocs(comment: string, docs: keyof typeof Docs) {
  return `${Docs[docs]}

${comment}`
}
function getDeprecationString(since: string, replacement: string) {
  return `@deprecated since ${since} please use \`${replacement}\``
}
const undefinedNote = `Note, that providing \`undefined\` is treated as the value not being there.
Read more here: https://pris.ly/d/null-undefined`

const JSDocFields = {
  take: (singular, plural) => addLinkToDocs(`Take \`±n\` ${plural} from the position of the cursor.`, 'pagination'),
  skip: (singular, plural) => addLinkToDocs(`Skip the first \`n\` ${plural}.`, 'pagination'),
  _count: (singular, plural) => addLinkToDocs(`Count returned ${plural}`, 'aggregations'),
  _avg: () => addLinkToDocs(`Select which fields to average`, 'aggregations'),
  _sum: () => addLinkToDocs(`Select which fields to sum`, 'aggregations'),
  _min: () => addLinkToDocs(`Select which fields to find the minimum value`, 'aggregations'),
  _max: () => addLinkToDocs(`Select which fields to find the maximum value`, 'aggregations'),
  count: () => getDeprecationString('2.23.0', '_count'),
  avg: () => getDeprecationString('2.23.0', '_avg'),
  sum: () => getDeprecationString('2.23.0', '_sum'),
  min: () => getDeprecationString('2.23.0', '_min'),
  max: () => getDeprecationString('2.23.0', '_max'),
  distinct: (singular, plural) => addLinkToDocs(`Filter by unique combinations of ${plural}.`, 'distinct'),
  orderBy: (singular, plural) => addLinkToDocs(`Determine the order of ${plural} to fetch.`, 'sorting'),
}
export const JSDocs: JSDocsType = {
  groupBy: {
    body: (ctx) => `Group by ${ctx.singular}.
${undefinedNote}
@param {${getGroupByArgsName(ctx.model.name)}} args - Group by arguments.
@example
// Group by city, order by createdAt, get count
const result = await prisma.user.groupBy({
  by: ['city', 'createdAt'],
  orderBy: {
    createdAt: true
  },
  _count: {
    _all: true
  },
})
`,
    fields: {},
  },
  create: {
    body: (ctx) => `Create a ${ctx.singular}.
@param {${getModelArgName(ctx.model.name, ctx.action)}} args - Arguments to create a ${ctx.singular}.
@example
// Create one ${ctx.singular}
const ${ctx.singular} = await ${ctx.method}({
  data: {
    // ... data to create a ${ctx.singular}
  }
})
`,
    fields: {
      data: (singular) => `The data needed to create a ${singular}.`,
    },
  },
  createMany: {
    body: (ctx) => `Create many ${ctx.plural}.
    @param {${getModelArgName(ctx.model.name, ctx.action)}} args - Arguments to create many ${ctx.plural}.
    @example
    // Create many ${ctx.plural}
    const ${lowerCase(ctx.mapping.model)} = await ${ctx.method}({
      data: {
        // ... provide data here
      }
    })
    `,
    fields: {
      data: (singular, plural) => `The data used to create many ${plural}.`,
    },
  },
  findUnique: {
    body: (ctx) =>
      `Find zero or one ${ctx.singular} that matches the filter.
@param {${getModelArgName(ctx.model.name, ctx.action)}} args - Arguments to find a ${ctx.singular}
@example
// Get one ${ctx.singular}
const ${lowerCase(ctx.mapping.model)} = await ${ctx.method}({
  where: {
    // ... provide filter here
  }
})`,
    fields: {
      where: (singular) => `Filter, which ${singular} to fetch.`,
    },
  },
  findUniqueOrThrow: {
    body: (ctx) =>
      `Find one ${ctx.singular} that matches the filter or throw an error  with \`error.code='P2025'\`
    if no matches were found.
@param {${getModelArgName(ctx.model.name, ctx.action)}} args - Arguments to find a ${ctx.singular}
@example
// Get one ${ctx.singular}
const ${lowerCase(ctx.mapping.model)} = await ${ctx.method}({
  where: {
    // ... provide filter here
  }
})`,
    fields: {
      where: (singular) => `Filter, which ${singular} to fetch.`,
    },
  },
  findFirst: {
    body: (ctx) =>
      `Find the first ${ctx.singular} that matches the filter.
${undefinedNote}
@param {${getModelArgName(ctx.model.name, ctx.action)}} args - Arguments to find a ${ctx.singular}
@example
// Get one ${ctx.singular}
const ${lowerCase(ctx.mapping.model)} = await ${ctx.method}({
  where: {
    // ... provide filter here
  }
})`,
    fields: {
      where: (singular) => `Filter, which ${singular} to fetch.`,
      orderBy: JSDocFields.orderBy,
      cursor: (singular, plural) => addLinkToDocs(`Sets the position for searching for ${plural}.`, 'cursor'),
      take: JSDocFields.take,
      skip: JSDocFields.skip,
      distinct: JSDocFields.distinct,
    },
  },
  findFirstOrThrow: {
    body: (ctx) =>
      `Find the first ${ctx.singular} that matches the filter or
throw \`PrismaKnownClientError\` with \`P2025\` code if no matches were found.
${undefinedNote}
@param {${getModelArgName(ctx.model.name, ctx.action)}} args - Arguments to find a ${ctx.singular}
@example
// Get one ${ctx.singular}
const ${lowerCase(ctx.mapping.model)} = await ${ctx.method}({
  where: {
    // ... provide filter here
  }
})`,
    fields: {
      where: (singular) => `Filter, which ${singular} to fetch.`,
      orderBy: JSDocFields.orderBy,
      cursor: (singular, plural) => addLinkToDocs(`Sets the position for searching for ${plural}.`, 'cursor'),
      take: JSDocFields.take,
      skip: JSDocFields.skip,
      distinct: JSDocFields.distinct,
    },
  },
  findMany: {
    body: (ctx) => {
      const onlySelect = ctx.firstScalar
        ? `\n// Only select the \`${ctx.firstScalar.name}\`
const ${lowerCase(ctx.mapping.model)}With${capitalize(ctx.firstScalar.name)}Only = await ${ctx.method}({ select: { ${
            ctx.firstScalar.name
          }: true } })`
        : ''

      return `Find zero or more ${ctx.plural} that matches the filter.
${undefinedNote}
@param {${getModelArgName(ctx.model.name, ctx.action)}=} args - Arguments to filter and select certain fields only.
@example
// Get all ${ctx.plural}
const ${ctx.mapping.plural} = await ${ctx.method}()

// Get first 10 ${ctx.plural}
const ${ctx.mapping.plural} = await ${ctx.method}({ take: 10 })
${onlySelect}
`
    },
    fields: {
      where: (singular, plural) => `Filter, which ${plural} to fetch.`,
      orderBy: JSDocFields.orderBy,
      skip: JSDocFields.skip,
      cursor: (singular, plural) => addLinkToDocs(`Sets the position for listing ${plural}.`, 'cursor'),
      take: JSDocFields.take,
    },
  },
  update: {
    body: (ctx) =>
      `Update one ${ctx.singular}.
@param {${getModelArgName(ctx.model.name, ctx.action)}} args - Arguments to update one ${ctx.singular}.
@example
// Update one ${ctx.singular}
const ${lowerCase(ctx.mapping.model)} = await ${ctx.method}({
  where: {
    // ... provide filter here
  },
  data: {
    // ... provide data here
  }
})
`,
    fields: {
      data: (singular) => `The data needed to update a ${singular}.`,
      where: (singular) => `Choose, which ${singular} to update.`,
    },
  },
  upsert: {
    body: (ctx) =>
      `Create or update one ${ctx.singular}.
@param {${getModelArgName(ctx.model.name, ctx.action)}} args - Arguments to update or create a ${ctx.singular}.
@example
// Update or create a ${ctx.singular}
const ${lowerCase(ctx.mapping.model)} = await ${ctx.method}({
  create: {
    // ... data to create a ${ctx.singular}
  },
  update: {
    // ... in case it already exists, update
  },
  where: {
    // ... the filter for the ${ctx.singular} we want to update
  }
})`,
    fields: {
      where: (singular) => `The filter to search for the ${singular} to update in case it exists.`,
      create: (singular) =>
        `In case the ${singular} found by the \`where\` argument doesn't exist, create a new ${singular} with this data.`,
      update: (singular) =>
        `In case the ${singular} was found with the provided \`where\` argument, update it with this data.`,
    },
  },
  delete: {
    body: (ctx) =>
      `Delete a ${ctx.singular}.
@param {${getModelArgName(ctx.model.name, ctx.action)}} args - Arguments to delete one ${ctx.singular}.
@example
// Delete one ${ctx.singular}
const ${ctx.singular} = await ${ctx.method}({
  where: {
    // ... filter to delete one ${ctx.singular}
  }
})
`,
    fields: {
      where: (singular) => `Filter which ${singular} to delete.`,
    },
  },
  aggregate: {
    body: (ctx) =>
      `Allows you to perform aggregations operations on a ${ctx.singular}.
${undefinedNote}
@param {${getModelArgName(
        ctx.model.name,
        ctx.action,
      )}} args - Select which aggregations you would like to apply and on what fields.
@example
// Ordered by age ascending
// Where email contains prisma.io
// Limited to the 10 users
const aggregations = await prisma.user.aggregate({
  _avg: {
    age: true,
  },
  where: {
    email: {
      contains: "prisma.io",
    },
  },
  orderBy: {
    age: "asc",
  },
  take: 10,
})`,
    fields: {
      where: (singular) => `Filter which ${singular} to aggregate.`,
      orderBy: JSDocFields.orderBy,
      cursor: () => addLinkToDocs(`Sets the start position`, 'cursor'),
      take: JSDocFields.take,
      skip: JSDocFields.skip,
      _count: JSDocFields._count,
      _avg: JSDocFields._avg,
      _sum: JSDocFields._sum,
      _min: JSDocFields._min,
      _max: JSDocFields._max,
      count: JSDocFields.count,
      avg: JSDocFields.avg,
      sum: JSDocFields.sum,
      min: JSDocFields.min,
      max: JSDocFields.max,
    },
  },
  count: {
    body: (ctx) =>
      `Count the number of ${ctx.plural}.
${undefinedNote}
@param {${getModelArgName(ctx.model.name, ctx.action)}} args - Arguments to filter ${ctx.plural} to count.
@example
// Count the number of ${ctx.plural}
const count = await ${ctx.method}({
  where: {
    // ... the filter for the ${ctx.plural} we want to count
  }
})`,
    fields: {},
  },
  updateMany: {
    body: (ctx) =>
      `Update zero or more ${ctx.plural}.
${undefinedNote}
@param {${getModelArgName(ctx.model.name, ctx.action)}} args - Arguments to update one or more rows.
@example
// Update many ${ctx.plural}
const ${lowerCase(ctx.mapping.model)} = await ${ctx.method}({
  where: {
    // ... provide filter here
  },
  data: {
    // ... provide data here
  }
})
`,
    fields: {
      data: (singular, plural) => `The data used to update ${plural}.`,
      where: (singular, plural) => `Filter which ${plural} to update`,
    },
  },
  deleteMany: {
    body: (ctx) =>
      `Delete zero or more ${ctx.plural}.
@param {${getModelArgName(ctx.model.name, ctx.action)}} args - Arguments to filter ${ctx.plural} to delete.
@example
// Delete a few ${ctx.plural}
const { count } = await ${ctx.method}({
  where: {
    // ... provide filter here
  }
})
`,
    fields: {
      where: (singular, plural) => `Filter which ${plural} to delete`,
    },
  },
  aggregateRaw: {
    body: (ctx) =>
      `Perform aggregation operations on a ${ctx.singular}.
@param {${getModelArgName(ctx.model.name, ctx.action)}} args - Select which aggregations you would like to apply.
@example
const ${lowerCase(ctx.mapping.model)} = await ${ctx.method}({
  pipeline: [
    { $match: { status: "registered" } },
    { $group: { _id: "$country", total: { $sum: 1 } } }
  ]
})`,
    fields: {
      pipeline: () =>
        'An array of aggregation stages to process and transform the document stream via the aggregation pipeline. ${@link https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline MongoDB Docs}.',
      options: () =>
        'Additional options to pass to the `aggregate` command ${@link https://docs.mongodb.com/manual/reference/command/aggregate/#command-fields MongoDB Docs}.',
    },
  },
  findRaw: {
    body: (ctx) =>
      `Find zero or more ${ctx.plural} that matches the filter.
@param {${getModelArgName(ctx.model.name, ctx.action)}} args - Select which filters you would like to apply.
@example
const ${lowerCase(ctx.mapping.model)} = await ${ctx.method}({
  filter: { age: { $gt: 25 } }
})`,
    fields: {
      filter: () =>
        'The query predicate filter. If unspecified, then all documents in the collection will match the predicate. ${@link https://docs.mongodb.com/manual/reference/operator/query MongoDB Docs}.',
      options: () =>
        'Additional options to pass to the `find` command ${@link https://docs.mongodb.com/manual/reference/command/find/#command-fields MongoDB Docs}.',
    },
  },
}


| 'freebsd11'
| 'freebsd12'
| 'freebsd13'
| 'freebsd14'
| 'openbsd'
| 'netbsd'
| 'arm'

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
}

datasource db {
  provider = "sqlite"
  url      = "file:./db"
}

model User {
  id    Int    @id @default(autoincrement())
  email String @unique
}


export type BinaryTarget =
  | 'native'
  | 'darwin'
  | 'darwin-arm64'
  | 'debian-openssl-1.0.x'
  | 'debian-openssl-1.1.x'
  | 'debian-openssl-3.0.x'
  | 'rhel-openssl-1.0.x'
  | 'rhel-openssl-1.1.x'
  | 'rhel-openssl-3.0.x'
  | 'linux-arm64-openssl-1.1.x'
  | 'linux-arm64-openssl-1.0.x'
  | 'linux-arm64-openssl-3.0.x'
  | 'linux-arm-openssl-1.1.x'
  | 'linux-arm-openssl-1.0.x'
  | 'linux-arm-openssl-3.0.x'
  | 'linux-musl'
  | 'linux-musl-openssl-3.0.x'
  | 'linux-musl-arm64-openssl-1.1.x'
  | 'linux-musl-arm64-openssl-3.0.x'
  | 'linux-nixos'
  | 'linux-static-x64'
  | 'linux-static-arm64'
  | 'windows'
  | 'freebsd11'
  | 'freebsd12'
  | 'freebsd13'
  | 'freebsd14'
  | 'openbsd'
  | 'netbsd'
  | 'arm'

export const binaryTargets: BinaryTarget[] = [
  'darwin',
  'darwin-arm64',
  'debian-openssl-1.0.x',
  'debian-openssl-1.1.x',
  'debian-openssl-3.0.x',
  'rhel-openssl-1.0.x',
  'rhel-openssl-1.1.x',
  'rhel-openssl-3.0.x',
  'linux-arm64-openssl-1.1.x',
  'linux-arm64-openssl-1.0.x',
  'linux-arm64-openssl-3.0.x',
  'linux-arm-openssl-1.1.x',
  'linux-arm-openssl-1.0.x',
  'linux-arm-openssl-3.0.x',
  'linux-musl',
  'linux-musl-openssl-3.0.x',
  'linux-musl-arm64-openssl-1.1.x',
  'linux-musl-arm64-openssl-3.0.x',
  'linux-nixos',
  'linux-static-x64',
  'linux-static-arm64',
  'windows',
  'freebsd11',
  'freebsd12',
  'freebsd13',
  'freebsd14',
  'openbsd',
  'netbsd',
  'arm',
]


import { defineMatrix } from '../_utils/defineMatrix'
import { Providers } from '../_utils/providers'

export default defineMatrix(() => [
  [
    {
      provider: Providers.POSTGRESQL,
    },
    {
      provider: Providers.MYSQL,
    },
    {
      provider: Providers.COCKROACHDB,
    },
    {
      provider: Providers.MONGODB,
    },
  ],
])


import { DataSource } from '@prisma/generator-helper'

type InjectableEnv = {
  parsed: {
    [x: string]: string | undefined
  }
}

/**
 * Builds an injectable environment for the data proxy edge client. It's useful
 * because it is designed to run in browser-like environments where things like
 * `fs`, `process.env`, and .env file loading are not available. That means env
 * vars are represented as a global variable or injected at build time. This is
 * the glue code to make this work with our existing env var loading logic. It
 * is the place where we make collect the env vars for the edge client. To
 * understand this better, take a look at the generated code in the edge client.
 * @see {@link declareInjectableEdgeEnv}
 * @param edge
 * @param datasources
 * @returns
 */
export function buildInjectableEdgeEnv(edge: boolean, datasources: DataSource[]) {
  if (edge === true) {
    return declareInjectableEdgeEnv(datasources)
  }

  return ``
}

/**
 * Creates the necessary declarations to embed env vars directly inside of the
 * generated client. We abuse a custom `JSON.stringify` to generate the code.
 * @param datasources to find env vars in
 */
function declareInjectableEdgeEnv(datasources: DataSource[]) {
  // we create a base env with empty values for env names
  const injectableEdgeEnv: InjectableEnv = { parsed: {} }
  const envVarNames = getSelectedEnvVarNames(datasources)

  for (const envVarName of envVarNames) {
    injectableEdgeEnv.parsed[envVarName] = getRuntimeEdgeEnvVar(envVarName)
  }

  // we make it json then remove the quotes to turn it into "code"
  const injectableEdgeEnvJson = JSON.stringify(injectableEdgeEnv, null, 2)
  const injectableEdgeEnvCode = injectableEdgeEnvJson.replace(/"/g, '')

  return `
config.injectableEdgeEnv = () => (${injectableEdgeEnvCode})`
}

/**
 * Determines which env vars we are interested in in the case of the data proxy
 * client. Right now, we are only interested in env vars from the `datasource`.
 * @param datasources to find env vars in
 * @returns
 */
function getSelectedEnvVarNames(datasources: DataSource[]) {
  return datasources.reduce((acc, datasource) => {
    if (datasource.url.fromEnvVar) {
      return [...acc, datasource.url.fromEnvVar]
    }

    return acc
  }, [] as string[])
}

/**
 * Builds the expression to get the value of an environment variable at run time.
 * @param envVarName Name of the environment variable
 */
export function getRuntimeEdgeEnvVar(envVarName: string) {
  // for cloudflare workers, an env var is a global js variable
  const cfwEnv = `typeof globalThis !== 'undefined' && globalThis['${envVarName}']`
  // for vercel edge functions, it's injected statically at build
  const nodeOrVercelEnv = `typeof process !== 'undefined' && process.env && process.env.${envVarName}`

  return `${cfwEnv} || ${nodeOrVercelEnv} || undefined`
}


import type { DMMF } from '../../dmmf-types'
import { TSClient } from '../../TSClient/TSClient'

/**
 * @internal
 * @remarks Used by, for example, the PDP to avoid child process calls to the CLI.
 */
export function dmmfToTypes(dmmf: DMMF.Document) {
  return new TSClient({
    dmmf,
    datasources: [],
    clientVersion: '',
    engineVersion: '',
    runtimeBase: '@prisma/client',
    runtimeNameJs: 'library',
    runtimeNameTs: 'library',
    schemaPath: '',
    outputDir: '',
    activeProvider: '',
    binaryPaths: {},
    generator: {
      binaryTargets: [],
      config: {},
      name: 'prisma-client-js',
      output: null,
      provider: { value: 'prisma-client-js', fromEnvVar: null },
      previewFeatures: [],
      isCustomOutput: false,
    },
    datamodel: '',
    browser: false,
    deno: false,
    edge: false,
    importWarning: false,
    wasm: false,
  }).toTS()
}


* The target distro is the Linux distro associated with the Prisma Engines.

* E.g., on Arch Linux, Debian, and Ubuntu, the target distro is `debian`. On Linux Alpine, the target distro is `musl`.

targetDistro?: 'rhel' | 'debian' | 'musl' | 'arm' | 'nixos' | 'freebsd11' | 'freebsd12' | 'freebsd13' | 'freebsd14'

keep keep keep keep replace keep keep keep keep

targetDistro?:
| 'rhel'
| 'debian'
| 'musl'
| 'arm'
| 'nixos'
| 'freebsd11'
| 'freebsd12'
| 'freebsd13'
| 'freebsd14'
| 'freebsd15'


import { faker } from '@faker-js/faker'

import testMatrix from './_matrix'
// @ts-ignore
import type { PrismaClient } from './node_modules/@prisma/client'

declare let prisma: PrismaClient
const missing = faker.database.mongodbObjectId()

testMatrix.setupTestSuite(() => {
  let id1: string
  let id2: string
  beforeAll(async () => {
    id1 = await prisma.user
      .create({
        data: {},
      })
      .then((user) => user.id)

    id2 = await prisma.user
      .create({
        data: {},
      })
      .then((user) => user.id)
  })

  test('batched errors are when all objects in batch are found', async () => {
    const found = prisma.user.findUniqueOrThrow({ where: { id: id1 } })
    const foundToo = prisma.user.findUniqueOrThrow({ where: { id: id2 } })
    const result = await Promise.allSettled([found, foundToo])
    expect(result).toEqual([
      { status: 'fulfilled', value: { id: id1 } },
      { status: 'fulfilled', value: { id: id2 } },
    ])
  })

  test('batched errors when some of the objects not found', async () => {
    const found = prisma.user.findUniqueOrThrow({ where: { id: id1 } })
    const notFound = prisma.user.findUniqueOrThrow({ where: { id: missing } })
    const newResult = await Promise.allSettled([found, notFound])
    expect(newResult).toEqual([
      { status: 'fulfilled', value: { id: id1 } },
      { status: 'rejected', reason: expect.objectContaining({ code: 'P2025' }) },
    ])
  })
}, {})


/* eslint-disable no-restricted-syntax */
/* eslint-disable import/no-cycle */
/* eslint-disable @typescript-eslint/naming-convention */
import { promisify } from 'util';
import { exec } from 'child_process';
import { access as fsAccess, mkdir as fsMkdir } from 'fs/promises';

import axios from 'axios';
import { UserSettings } from 'n8n-core';
import { LoggerProxy, PublicInstalledPackage } from 'n8n-workflow';

import {
	NODE_PACKAGE_PREFIX,
	NPM_COMMAND_TOKENS,
	NPM_PACKAGE_STATUS_GOOD,
	RESPONSE_ERROR_MESSAGES,
	UNKNOWN_FAILURE_REASON,
} from '../constants';
import { InstalledPackages } from '../databases/entities/InstalledPackages';
import config from '../../config';

import type { CommunityPackages } from '../Interfaces';

const {
	PACKAGE_NAME_NOT_PROVIDED,
	DISK_IS_FULL,
	PACKAGE_FAILED_TO_INSTALL,
	PACKAGE_VERSION_NOT_FOUND,
	PACKAGE_DOES_NOT_CONTAIN_NODES,
	PACKAGE_NOT_FOUND,
} = RESPONSE_ERROR_MESSAGES;

const {
	NPM_PACKAGE_NOT_FOUND_ERROR,
	NPM_NO_VERSION_AVAILABLE,
	NPM_DISK_NO_SPACE,
	NPM_DISK_INSUFFICIENT_SPACE,
	NPM_PACKAGE_VERSION_NOT_FOUND_ERROR,
} = NPM_COMMAND_TOKENS;

const execAsync = promisify(exec);

const INVALID_OR_SUSPICIOUS_PACKAGE_NAME = /[^0-9a-z@\-./]/;

export const parseNpmPackageName = (rawString?: string): CommunityPackages.ParsedPackageName => {
	if (!rawString) throw new Error(PACKAGE_NAME_NOT_PROVIDED);

	if (INVALID_OR_SUSPICIOUS_PACKAGE_NAME.test(rawString))
		throw new Error('Package name must be a single word');

	const scope = rawString.includes('/') ? rawString.split('/')[0] : undefined;

	const packageNameWithoutScope = scope ? rawString.replace(`${scope}/`, '') : rawString;

	if (!packageNameWithoutScope.startsWith(NODE_PACKAGE_PREFIX)) {
		throw new Error(`Package name must start with ${NODE_PACKAGE_PREFIX}`);
	}

	const version = packageNameWithoutScope.includes('@')
		? packageNameWithoutScope.split('@')[1]
		: undefined;

	const packageName = version ? rawString.replace(`@${version}`, '') : rawString;

	return {
		packageName,
		scope,
		version,
		rawString,
	};
};

export const sanitizeNpmPackageName = parseNpmPackageName;

export const executeCommand = async (
	command: string,
	options?: { doNotHandleError?: boolean },
): Promise<string> => {
	const downloadFolder = UserSettings.getUserN8nFolderDowloadedNodesPath();

	try {
		await fsAccess(downloadFolder);
	} catch (_) {
		await fsMkdir(downloadFolder);
	}

	const execOptions = {
		cwd: downloadFolder,
		env: {
			NODE_PATH: process.env.NODE_PATH,
			PATH: process.env.PATH,
		},
	};

	try {
		const commandResult = await execAsync(command, execOptions);

		return commandResult.stdout;
	} catch (error) {
		if (options?.doNotHandleError) throw error;

		const errorMessage = error instanceof Error ? error.message : UNKNOWN_FAILURE_REASON;

		const map = {
			[NPM_PACKAGE_NOT_FOUND_ERROR]: PACKAGE_NOT_FOUND,
			[NPM_NO_VERSION_AVAILABLE]: PACKAGE_NOT_FOUND,
			[NPM_PACKAGE_VERSION_NOT_FOUND_ERROR]: PACKAGE_VERSION_NOT_FOUND,
			[NPM_DISK_NO_SPACE]: DISK_IS_FULL,
			[NPM_DISK_INSUFFICIENT_SPACE]: DISK_IS_FULL,
		};

		Object.entries(map).forEach(([npmMessage, n8nMessage]) => {
			if (errorMessage.includes(npmMessage)) throw new Error(n8nMessage);
		});

		LoggerProxy.warn('npm command failed', { errorMessage });

		throw new Error(PACKAGE_FAILED_TO_INSTALL);
	}
};

export function matchPackagesWithUpdates(
	packages: InstalledPackages[],
	updates?: CommunityPackages.AvailableUpdates,
): PublicInstalledPackage[] {
	if (!updates) return packages;

	return packages.reduce<PublicInstalledPackage[]>((acc, cur) => {
		const publicPackage: PublicInstalledPackage = { ...cur };

		const update = updates[cur.packageName];

		if (update) publicPackage.updateAvailable = update.latest;

		acc.push(publicPackage);

		return acc;
	}, []);
}

export function matchMissingPackages(
	installedPackages: PublicInstalledPackage[],
	missingPackages: string,
): PublicInstalledPackage[] {
	const missingPackageNames = missingPackages.split(' ');

	const missingPackagesList = missingPackageNames.map((missingPackageName: string) => {
		// Strip away versions but maintain scope and package name
		try {
			const parsedPackageData = parseNpmPackageName(missingPackageName);
			return parsedPackageData.packageName;

			// eslint-disable-next-line no-empty
		} catch (_) {}
		return undefined;
	});

	const hydratedPackageList = [] as PublicInstalledPackage[];

	installedPackages.forEach((installedPackage) => {
		const hydratedInstalledPackage = { ...installedPackage };
		if (missingPackagesList.includes(hydratedInstalledPackage.packageName)) {
			hydratedInstalledPackage.failedLoading = true;
		}
		hydratedPackageList.push(hydratedInstalledPackage);
	});

	return hydratedPackageList;
}

export async function checkNpmPackageStatus(
	packageName: string,
): Promise<CommunityPackages.PackageStatusCheck> {
	const N8N_BACKEND_SERVICE_URL = 'https://api.n8n.io/api/package';

	try {
		const response = await axios.post<CommunityPackages.PackageStatusCheck>(
			N8N_BACKEND_SERVICE_URL,
			{ name: packageName },
			{ method: 'POST' },
		);

		if (response.data.status !== NPM_PACKAGE_STATUS_GOOD) return response.data;
	} catch (error) {
		// Do nothing if service is unreachable
	}

	return { status: NPM_PACKAGE_STATUS_GOOD };
}

export function hasPackageLoaded(packageName: string): boolean {
	const missingPackages = config.get('nodes.packagesMissing') as string | undefined;

	if (!missingPackages) return true;

	return !missingPackages
		.split(' ')
		.some(
			(packageNameAndVersion) =>
				packageNameAndVersion.startsWith(packageName) &&
				packageNameAndVersion.replace(packageName, '').startsWith('@'),
		);
}

export function removePackageFromMissingList(packageName: string): void {
	try {
		const failedPackages = (config.get('nodes.packagesMissing') as string).split(' ');

		const packageFailedToLoad = failedPackages.filter(
			(packageNameAndVersion) =>
				!packageNameAndVersion.startsWith(packageName) ||
				!packageNameAndVersion.replace(packageName, '').startsWith('@'),
		);

		config.set('nodes.packagesMissing', packageFailedToLoad.join(' '));
	} catch (_error) {
		// Do nothing
	}
}

export const isClientError = (error: Error): boolean => {
	const clientErrors = [
		PACKAGE_VERSION_NOT_FOUND,
		PACKAGE_DOES_NOT_CONTAIN_NODES,
		PACKAGE_NOT_FOUND,
	];

	return clientErrors.some((message) => error.message.includes(message));
};

export function isNpmError(error: unknown): error is { code: number; stdout: string } {
	return typeof error === 'object' && error !== null && 'code' in error && 'stdout' in error;
}


@use "mixins/mixins";
@use "./common/var";

$typeMap: (
	primary: var.$link-primary-font-color,
	danger: var.$link-danger-font-color,
	success: var.$link-success-font-color,
	warning: var.$link-warning-font-color,
	info: var.$link-info-font-color,
);

@include mixins.b(link) {
	display: inline-flex;
	flex-direction: row;
	align-items: center;
	justify-content: center;
	vertical-align: middle;
	position: relative;
	text-decoration: none;
	outline: none;
	cursor: pointer;
	padding: 0;
	font-size: var.$link-font-size;
	font-weight: var.$link-font-weight;

	@include mixins.when(underline) {
		&:hover:after {
			content: '';
			position: absolute;
			left: 0;
			right: 0;
			height: 0;
			bottom: 0;
			border-bottom: 1px solid var.$link-default-active-color;
		}
	}

	@include mixins.when(disabled) {
		cursor: not-allowed;
	}

	& [class*='el-icon-'] {
		& + span {
			margin-left: 5px;
		}
	}

	&.el-link--default {
		color: var.$link-default-font-color;
		&:hover {
			color: var.$link-default-active-color;
		}
		&:after {
			border-color: var.$link-default-active-color;
		}
		@include mixins.when(disabled) {
			color: var.$link-disabled-font-color;
		}
	}

	@each $type, $primaryColor in $typeMap {
		&.el-link--#{$type} {
			color: $primaryColor;
			&:hover {
				color: var.$color-primary-light-3;
			}
			&:after {
				border-color: $primaryColor;
			}
			@include mixins.when(disabled) {
				color: var.$color-primary-light-6;
			}
			@include mixins.when(underline) {
				&:hover:after {
					border-color: $primaryColor;
				}
			}
		}
	}
}


/* tslint:disable:variable-name */

import N8nNotice from './Notice.vue';
import {StoryFn} from "@storybook/vue";

export default {
	title: 'Atoms/Notice',
	component: N8nNotice,
	argTypes: {
		theme: {
			control: 'select',
			options: ['success', 'warning', 'danger', 'info'],
		},
	},
};

const SlotTemplate: StoryFn = (args, {argTypes}) => ({
	props: Object.keys(argTypes),
	components: {
		N8nNotice,
	},
	template: `<n8n-notice v-bind="$props">This is a notice! Thread carefully from this point forward.</n8n-notice>`,
});

const PropTemplate: StoryFn = (args, {argTypes}) => ({
	props: Object.keys(argTypes),
	components: {
		N8nNotice,
	},
	template: `<n8n-notice v-bind="$props"/>`,
});

export const Warning = SlotTemplate.bind({});
Warning.args = {
	theme: 'warning',
};

export const Danger = SlotTemplate.bind({});
Danger.args = {
	theme: 'danger',
};

export const Success = SlotTemplate.bind({});
Success.args = {
	theme: 'success',
};

export const Info = SlotTemplate.bind({});
Info.args = {
	theme: 'info',
};

export const Sanitized = PropTemplate.bind({});
Sanitized.args = {
	theme: 'warning',
	content: ' This content contains a script tag and is sanitized.',
};

export const Truncated = PropTemplate.bind({});
Truncated.args = {
	theme: 'warning',
	truncate: true,
	content: 'This content is long and will be truncated at 150 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
};

export const HtmlEdgeCase = PropTemplate.bind({});
HtmlEdgeCase.args = {
	theme: 'warning',
	truncate: true,
	content: 'This content is long and will be truncated at 150 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod read the documentation ut labore et dolore magna aliqua.',
};


import {
	INodeProperties,
} from 'n8n-workflow';

export const userOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		noDataExpression: true,
		default: 'getAll',
		options: [
			{
				name: 'Get All',
				value: 'getAll',
				description: 'Retrieve data on all the users in the logged-in user\'s organization',
				action: 'Get all users',
			},
		],
		displayOptions: {
			show: {
				resource: [
					'user',
				],
			},
		},
	},
];

export const userFields: INodeProperties[] = [
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		default: false,
		description: 'Whether to return all results or only up to a given limit',
		displayOptions: {
			show: {
				resource: [
					'user',
				],
				operation: [
					'getAll',
				],
			},
		},
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		default: 5,
		description: 'Max number of results to return',
		typeOptions: {
			minValue: 1,
			maxValue: 1000,
		},
		displayOptions: {
			show: {
				resource: [
					'user',
				],
				operation: [
					'getAll',
				],
				returnAll: [
					false,
				],
			},
		},
	},
];


// Make sure the imported modules can resolve dependencies fine.

this.nodeModulesPath = await this.getNodeModulesFolderLocation();

const delimiter = process.platform === 'win32' ? ';' : ':';

/* eslint-disable import/no-cycle */
import express from 'express';
import { PublicInstalledPackage } from 'n8n-workflow';

import config from '../../config';
import { ResponseHelper, LoadNodesAndCredentials, Push, InternalHooksManager } from '..';

import { RESPONSE_ERROR_MESSAGES, UNKNOWN_FAILURE_REASON } from '../constants';
import {
	matchMissingPackages,
	matchPackagesWithUpdates,
	executeCommand,
	checkNpmPackageStatus,
	hasPackageLoaded,
	removePackageFromMissingList,
	parseNpmPackageName,
	isClientError,
	sanitizeNpmPackageName,
	isNpmError,
} from '../CommunityNodes/helpers';
import {
	getAllInstalledPackages,
	findInstalledPackage,
	isPackageInstalled,
} from '../CommunityNodes/packageModel';
import { isAuthenticatedRequest } from '../UserManagement/UserManagementHelper';

import type { NodeRequest } from '../requests';
import type { CommunityPackages } from '../Interfaces';
import { InstalledPackages } from '../databases/entities/InstalledPackages';

const { PACKAGE_NOT_INSTALLED, PACKAGE_NAME_NOT_PROVIDED } = RESPONSE_ERROR_MESSAGES;

export const nodesController = express.Router();

nodesController.use((req, res, next) => {
	if (!isAuthenticatedRequest(req) || req.user.globalRole.name !== 'owner') {
		res.status(403).json({ status: 'error', message: 'Unauthorized' });
		return;
	}

	next();
});

nodesController.use((req, res, next) => {
	if (config.getEnv('executions.mode') === 'queue' && req.method !== 'GET') {
		res.status(400).json({
			status: 'error',
			message: 'Package management is disabled when running in "queue" mode',
		});
		return;
	}

	next();
});

/**
 * POST /nodes
 *
 * Install an n8n community package
 */
nodesController.post(
	'/',
	ResponseHelper.send(async (req: NodeRequest.Post) => {
		const { name } = req.body;

		if (!name) {
			throw new ResponseHelper.ResponseError(PACKAGE_NAME_NOT_PROVIDED, undefined, 400);
		}

		let parsed: CommunityPackages.ParsedPackageName;

		try {
			parsed = parseNpmPackageName(name);
		} catch (error) {
			throw new ResponseHelper.ResponseError(
				error instanceof Error ? error.message : 'Failed to parse package name',
				undefined,
				400,
			);
		}

		const isInstalled = await isPackageInstalled(parsed.packageName);
		const hasLoaded = hasPackageLoaded(name);

		if (isInstalled && hasLoaded) {
			throw new ResponseHelper.ResponseError(
				[
					`Package "${parsed.packageName}" is already installed`,
					'To update it, click the corresponding button in the UI',
				].join('.'),
				undefined,
				400,
			);
		}

		const packageStatus = await checkNpmPackageStatus(name);

		if (packageStatus.status !== 'OK') {
			throw new ResponseHelper.ResponseError(
				`Package "${name}" is banned so it cannot be installed`,
				undefined,
				400,
			);
		}

		let installedPackage: InstalledPackages;

		try {
			installedPackage = await LoadNodesAndCredentials().loadNpmModule(
				parsed.packageName,
				parsed.version,
			);
		} catch (error) {
			const errorMessage = error instanceof Error ? error.message : UNKNOWN_FAILURE_REASON;

			void InternalHooksManager.getInstance().onCommunityPackageInstallFinished({
				user_id: req.user.id,
				input_string: name,
				package_name: parsed.packageName,
				success: false,
				package_version: parsed.version,
				failure_reason: errorMessage,
			});

			const message = [`Error loading package "${name}"`, errorMessage].join(':');

			const clientError = error instanceof Error ? isClientError(error) : false;

			throw new ResponseHelper.ResponseError(message, undefined, clientError ? 400 : 500);
		}

		if (!hasLoaded) removePackageFromMissingList(name);

		const pushInstance = Push.getInstance();

		// broadcast to connected frontends that node list has been updated
		installedPackage.installedNodes.forEach((node) => {
			pushInstance.send('reloadNodeType', {
				name: node.type,
				version: node.latestVersion,
			});
		});

		void InternalHooksManager.getInstance().onCommunityPackageInstallFinished({
			user_id: req.user.id,
			input_string: name,
			package_name: parsed.packageName,
			success: true,
			package_version: parsed.version,
			package_node_names: installedPackage.installedNodes.map((node) => node.name),
			package_author: installedPackage.authorName,
			package_author_email: installedPackage.authorEmail,
		});

		return installedPackage;
	}),
);

/**
 * GET /nodes
 *
 * Retrieve list of installed n8n community packages
 */
nodesController.get(
	'/',
	ResponseHelper.send(async (): Promise<PublicInstalledPackage[]> => {
		const installedPackages = await getAllInstalledPackages();

		if (installedPackages.length === 0) return [];

		let pendingUpdates: CommunityPackages.AvailableUpdates | undefined;

		try {
			const command = ['npm', 'outdated', '--json'].join(' ');
			await executeCommand(command, { doNotHandleError: true });
		} catch (error) {
			// when there are updates, npm exits with code 1
			// when there are no updates, command succeeds
			// https://github.com/npm/rfcs/issues/473

			if (isNpmError(error) && error.code === 1) {
				pendingUpdates = JSON.parse(error.stdout) as CommunityPackages.AvailableUpdates;
			}
		}

		let hydratedPackages = matchPackagesWithUpdates(installedPackages, pendingUpdates);

		try {
			const missingPackages = config.get('nodes.packagesMissing') as string | undefined;

			if (missingPackages) {
				hydratedPackages = matchMissingPackages(hydratedPackages, missingPackages);
			}
		} catch (_) {
			// Do nothing if setting is missing
		}

		return hydratedPackages;
	}),
);

/**
 * DELETE /nodes
 *
 * Uninstall an installed n8n community package
 */
nodesController.delete(
	'/',
	ResponseHelper.send(async (req: NodeRequest.Delete) => {
		const { name } = req.body;

		if (!name) {
			throw new ResponseHelper.ResponseError(PACKAGE_NAME_NOT_PROVIDED, undefined, 400);
		}

		try {
			sanitizeNpmPackageName(name);
		} catch (error) {
			const message = error instanceof Error ? error.message : UNKNOWN_FAILURE_REASON;

			throw new ResponseHelper.ResponseError(message, undefined, 400);
		}

		const installedPackage = await findInstalledPackage(name);

		if (!installedPackage) {
			throw new ResponseHelper.ResponseError(PACKAGE_NOT_INSTALLED, undefined, 400);
		}

		try {
			await LoadNodesAndCredentials().removeNpmModule(name, installedPackage);
		} catch (error) {
			const message = [
				`Error removing package "${name}"`,
				error instanceof Error ? error.message : UNKNOWN_FAILURE_REASON,
			].join(':');

			throw new ResponseHelper.ResponseError(message, undefined, 500);
		}

		const pushInstance = Push.getInstance();

		// broadcast to connected frontends that node list has been updated
		installedPackage.installedNodes.forEach((node) => {
			pushInstance.send('removeNodeType', {
				name: node.type,
				version: node.latestVersion,
			});
		});

		void InternalHooksManager.getInstance().onCommunityPackageDeleteFinished({
			user_id: req.user.id,
			package_name: name,
			package_version: installedPackage.installedVersion,
			package_node_names: installedPackage.installedNodes.map((node) => node.name),
			package_author: installedPackage.authorName,
			package_author_email: installedPackage.authorEmail,
		});
	}),
);

/**
 * PATCH /nodes
 *
 * Update an installed n8n community package
 */
nodesController.patch(
	'/',
	ResponseHelper.send(async (req: NodeRequest.Update) => {
		const { name } = req.body;

		if (!name) {
			throw new ResponseHelper.ResponseError(PACKAGE_NAME_NOT_PROVIDED, undefined, 400);
		}

		const previouslyInstalledPackage = await findInstalledPackage(name);

		if (!previouslyInstalledPackage) {
			throw new ResponseHelper.ResponseError(PACKAGE_NOT_INSTALLED, undefined, 400);
		}

		try {
			const newInstalledPackage = await LoadNodesAndCredentials().updateNpmModule(
				parseNpmPackageName(name).packageName,
				previouslyInstalledPackage,
			);

			const pushInstance = Push.getInstance();

			// broadcast to connected frontends that node list has been updated
			previouslyInstalledPackage.installedNodes.forEach((node) => {
				pushInstance.send('removeNodeType', {
					name: node.type,
					version: node.latestVersion,
				});
			});

			newInstalledPackage.installedNodes.forEach((node) => {
				pushInstance.send('reloadNodeType', {
					name: node.name,
					version: node.latestVersion,
				});
			});

			void InternalHooksManager.getInstance().onCommunityPackageUpdateFinished({
				user_id: req.user.id,
				package_name: name,
				package_version_current: previouslyInstalledPackage.installedVersion,
				package_version_new: newInstalledPackage.installedVersion,
				package_node_names: newInstalledPackage.installedNodes.map((node) => node.name),
				package_author: newInstalledPackage.authorName,
				package_author_email: newInstalledPackage.authorEmail,
			});

			return newInstalledPackage;
		} catch (error) {
			previouslyInstalledPackage.installedNodes.forEach((node) => {
				const pushInstance = Push.getInstance();
				pushInstance.send('removeNodeType', {
					name: node.type,
					version: node.latestVersion,
				});
			});

			const message = [
				`Error removing package "${name}"`,
				error instanceof Error ? error.message : UNKNOWN_FAILURE_REASON,
			].join(':');

			throw new ResponseHelper.ResponseError(message, undefined, 500);
		}
	}),
);


import { INodeProperties } from 'n8n-workflow';

export const employeeGetAllDescription: INodeProperties[] = [
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		default: false,
		displayOptions: {
			show: {
				resource: ['employee'],
				operation: ['getAll'],
			},
		},
		description: 'Whether to return all results or only up to a given limit',
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		default: 5,
		typeOptions: {
			minValue: 1,
			maxValue: 1000,
		},
		displayOptions: {
			show: {
				resource: ['employee'],
				operation: ['getAll'],
				returnAll: [false],
			},
		},
		description: 'Max number of results to return',
	},
];


import {
	OptionsWithUri,
} from 'request';

import {
	IExecuteFunctions,
	IExecuteSingleFunctions,
	ILoadOptionsFunctions,
} from 'n8n-core';

import {
	IDataObject,
	IOAuth2Options,
	JsonObject,
	NodeApiError,
	NodeOperationError,
} from 'n8n-workflow';

import _ from 'lodash';

export async function slackApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: object = {}, query: object = {}, headers: {} | undefined = undefined, option: {} = {}): Promise<any> { // tslint:disable-line:no-any
	const authenticationMethod = this.getNodeParameter('authentication', 0, 'accessToken') as string;
	let options: OptionsWithUri = {
		method,
		headers: headers || {
			'Content-Type': 'application/json; charset=utf-8',
		},
		body,
		qs: query,
		uri: `https://slack.com/api${resource}`,
		json: true,
	};
	options = Object.assign({}, options, option);
	if (Object.keys(body).length === 0) {
		delete options.body;
	}
	if (Object.keys(query).length === 0) {
		delete options.qs;
	}

	const oAuth2Options: IOAuth2Options = {
		tokenType: 'Bearer',
		property: 'authed_user.access_token',
	};

	try {
		let response: any; // tslint:disable-line:no-any
		const credentialType = authenticationMethod === 'accessToken' ? 'slackApi' : 'slackOAuth2Api';
		response = await this.helpers.requestWithAuthentication.call(this, credentialType, options, { oauth2: oAuth2Options });

		if (response.ok === false) {
			if (response.error === 'paid_teams_only') {
				throw new NodeOperationError(this.getNode(), `Your current Slack plan does not include the resource '${this.getNodeParameter('resource', 0) as string}'`, {
					description: `Hint: Upgrate to the Slack plan that includes the funcionality you want to use.`,
				});
			}

			throw new NodeOperationError(this.getNode(), 'Slack error response: ' + JSON.stringify(response));
		}

		return response;
	} catch (error) {
		throw new NodeApiError(this.getNode(), error as JsonObject);
	}
}

export async function slackApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
	const returnData: IDataObject[] = [];
	let responseData;
	query.page = 1;
	//if the endpoint uses legacy pagination use count
	//https://api.slack.com/docs/pagination#classic
	if (endpoint.includes('files.list')) {
		query.count = 100;
	} else {
		query.limit = 100;
	}
	do {
		responseData = await slackApiRequest.call(this, method, endpoint, body, query);
		query.cursor = _.get(responseData, 'response_metadata.next_cursor');
		query.page++;
		returnData.push.apply(returnData, responseData[propertyName]);
	} while (
		(responseData.response_metadata !== undefined &&
			responseData.response_metadata.next_cursor !== undefined &&
			responseData.response_metadata.next_cursor !== '' &&
			responseData.response_metadata.next_cursor !== null) ||
		(responseData.paging !== undefined &&
			responseData.paging.pages !== undefined &&
			responseData.paging.page !== undefined &&
			responseData.paging.page < responseData.paging.pages
		)
	);

	return returnData;
}

export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any
	let result;
	try {
		result = JSON.parse(json!);
	} catch (exception) {
		result = undefined;
	}
	return result;
}


import { ResponseHelper, LoadNodesAndCredentials, Push, InternalHooksManager } from '..';

import { RESPONSE_ERROR_MESSAGES, UNKNOWN_FAILURE_REASON } from '../constants';


@charset "UTF-8";
@use 'common/var';
@use 'mixins/button';
@use 'mixins/mixins';
@use 'mixins/utils';
@use 'mixins/function';

@include mixins.b(button-group) {
	@include utils.utils-clearfix;
	display: inline-block;
	vertical-align: middle;

	& > .el-button {
		float: left;
		position: relative;
		& + .el-button {
			margin-left: 0;
		}
		&.is-disabled {
			z-index: 1;
		}
		&:first-child {
			border-top-right-radius: 0;
			border-bottom-right-radius: 0;
		}
		&:last-child {
			border-top-left-radius: 0;
			border-bottom-left-radius: 0;
		}
		&:first-child:last-child {
			border-top-right-radius: var(--border-radius-base);
			border-bottom-right-radius: var(--border-radius-base);
			border-top-left-radius: var(--border-radius-base);
			border-bottom-left-radius: var(--border-radius-base);

			&.is-round {
				border-radius: 20px;
			}

			&.is-circle {
				border-radius: 50%;
			}
		}
		&:not(:first-child):not(:last-child) {
			border-radius: 0;
		}
		&:not(:last-child) {
			margin-right: -1px;
		}

		&:hover,
		&:focus,
		&:active {
			z-index: 1;
		}

		@include mixins.when(active) {
			z-index: 1;
		}
	}

	& > .el-dropdown {
		& > .el-button {
			border-top-left-radius: 0;
			border-bottom-left-radius: 0;
			border-left-color: rgba(var.$color-white, 0.5);
		}
	}

	@each $type in (primary, success, warning, danger, info) {
		.el-button--#{$type} {
			&:first-child {
				border-right-color: rgba(var.$color-white, 0.5);
			}
			&:last-child {
				border-left-color: rgba(var.$color-white, 0.5);
			}
			&:not(:first-child):not(:last-child) {
				border-left-color: rgba(var.$color-white, 0.5);
				border-right-color: rgba(var.$color-white, 0.5);
			}
		}
	}
}


import {
	INodeProperties,
} from 'n8n-workflow';

export const purchaseOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		noDataExpression: true,
		default: 'get',
		options: [
			{
				name: 'Get',
				value: 'get',
				action: 'Get a purchase',
			},
			{
				name: 'Get All',
				value: 'getAll',
				action: 'Get all purchases',
			},
		],
		displayOptions: {
			show: {
				resource: [
					'purchase',
				],
			},
		},
	},
];

export const purchaseFields: INodeProperties[] = [
	// ----------------------------------
	//         purchase: get
	// ----------------------------------
	{
		displayName: 'Purchase ID',
		name: 'purchaseId',
		type: 'string',
		required: true,
		default: '',
		description: 'The ID of the purchase to retrieve',
		displayOptions: {
			show: {
				resource: [
					'purchase',
				],
				operation: [
					'get',
				],
			},
		},
	},

	// ----------------------------------
	//         purchase: getAll
	// ----------------------------------
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		default: false,
		description: 'Whether to return all results or only up to a given limit',
		displayOptions: {
			show: {
				resource: [
					'purchase',
				],
				operation: [
					'getAll',
				],
			},
		},
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		default: 50,
		description: 'Max number of results to return',
		typeOptions: {
			minValue: 1,
			maxValue: 1000,
		},
		displayOptions: {
			show: {
				resource: [
					'purchase',
				],
				operation: [
					'getAll',
				],
				returnAll: [
					false,
				],
			},
		},
	},
	{
		displayName: 'Filters',
		name: 'filters',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		options: [
			{
				displayName: 'Query',
				name: 'query',
				type: 'string',
				default: '',
				placeholder: 'WHERE Metadata.LastUpdatedTime > \'2021-01-01\'',
				description: 'The condition for selecting purchases. See the guide for supported syntax.',
				typeOptions: {
					alwaysOpenEditWindow: true,
				},
			},
		],
		displayOptions: {
			show: {
				resource: [
					'purchase',
				],
				operation: [
					'getAll',
				],
			},
		},
	},
];


import {
	CloseHandler,
	CreateHandler,
	DeleteHandler,
	GetAllHandler,
	GetHandler,
	MoveHandler,
	ReopenHandler,
	SyncHandler,
	UpdateHandler
} from './OperationHandler';

import {Context} from './GenericFunctions';
import { IDataObject } from 'n8n-workflow';

export class TodoistService implements Service {

	async execute(ctx: Context, operation: OperationType, itemIndex: number): Promise<TodoistResponse> {
		return this.handlers[operation].handleOperation(ctx, itemIndex);
	}

	private handlers = {
		'create': new CreateHandler(),
		'close': new CloseHandler(),
		'delete': new DeleteHandler(),
		'get': new GetHandler(),
		'getAll': new GetAllHandler(),
		'reopen': new ReopenHandler(),
		'update': new UpdateHandler(),
		'move': new MoveHandler(),
		'sync': new SyncHandler(),
	};

}

export enum OperationType {
	create = 'create',
	close = 'close',
	delete = 'delete',
	get = 'get',
	getAll = 'getAll',
	reopen = 'reopen',
	update = 'update',
	move = 'move',
	sync = 'sync',
}

export interface Section {
	name: string;
	id: string;
}

export interface Service {
	execute(ctx: Context, operation: OperationType, itemIndex: number): Promise<TodoistResponse>;
}

export interface TodoistResponse {
	success?: boolean;
	data?: IDataObject;
}


const isInstalled = await isPackageInstalled(parsed.packageName);

const hasLoaded = hasPackageLoaded(name);

if (parsed.packageName === STARTER_TEMPLATE_NAME) {

`Package "${parsed.packageName}" is only a template`,

'Please enter an actual package to install',

/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/naming-convention */

import { RESPONSE_ERROR_MESSAGES as CORE_RESPONSE_ERROR_MESSAGES } from 'n8n-core';

export const NODE_PACKAGE_PREFIX = 'n8n-nodes-';

export const RESPONSE_ERROR_MESSAGES = {
	NO_CREDENTIAL: 'Credential not found',
	NO_ENCRYPTION_KEY: CORE_RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY,
	PACKAGE_NAME_NOT_PROVIDED: 'Package name is required',
	PACKAGE_NAME_NOT_VALID: `Package name is not valid - it must start with "${NODE_PACKAGE_PREFIX}"`,
	PACKAGE_NOT_INSTALLED: 'This package is not installed - you must install it first',
	PACKAGE_FAILED_TO_INSTALL: 'Package could not be installed - check logs for details',
	PACKAGE_NOT_FOUND: 'Package not found in npm',
	PACKAGE_VERSION_NOT_FOUND: 'The specified package version was not found',
	PACKAGE_DOES_NOT_CONTAIN_NODES: 'The specified package does not contain any nodes',
	DISK_IS_FULL: 'There appears to be insufficient disk space',
};

export const AUTH_COOKIE_NAME = 'n8n-auth';

export const NPM_COMMAND_TOKENS = {
	NPM_PACKAGE_NOT_FOUND_ERROR: '404 Not Found',
	NPM_PACKAGE_VERSION_NOT_FOUND_ERROR: 'No matching version found for',
	NPM_NO_VERSION_AVAILABLE: 'No valid versions available',
	NPM_DISK_NO_SPACE: 'ENOSPC',
	NPM_DISK_INSUFFICIENT_SPACE: 'insufficient space',
};

export const NPM_PACKAGE_STATUS_GOOD = 'OK';

export const UNKNOWN_FAILURE_REASON = 'Unknown failure reason';


import { INodeProperties } from 'n8n-workflow';

export const linkOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		noDataExpression: true,
		displayOptions: {
			show: {
				resource: ['link'],
			},
		},
		options: [
			{
				name: 'Create',
				value: 'create',
				description: 'Create a link',
				action: 'Create a link',
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Get a link',
				action: 'Get a link',
			},
			{
				name: 'Update',
				value: 'update',
				description: 'Update a link',
				action: 'Update a link',
			},
		],
		default: 'create',
	},
];

export const linkFields: INodeProperties[] = [
	/* -------------------------------------------------------------------------- */
	/*                                link:create                                 */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Long URL',
		name: 'longUrl',
		type: 'string',
		displayOptions: {
			show: {
				resource: ['link'],
				operation: ['create'],
			},
		},
		default: '',
		placeholder: 'https://example.com',
		required: true,
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				resource: ['link'],
				operation: ['create'],
			},
		},
		options: [
			{
				displayName: 'Domain',
				name: 'domain',
				type: 'string',
				default: 'bit.ly',
			},
			{
				displayName: 'Group Name or ID',
				name: 'group',
				type: 'options',
				description:
					'Choose from the list, or specify an ID using an expression',
				default: '',
				typeOptions: {
					loadOptionsMethod: 'getGroups',
				},
			},
			{
				displayName: 'Tag Names or IDs',
				name: 'tags',
				type: 'multiOptions',
				description:
					'Choose from the list, or specify IDs using an expression',
				default: [],
				typeOptions: {
					loadOptionsMethod: 'getTags',
					loadOptionsDependsOn: ['group'],
				},
			},
			{
				displayName: 'Title',
				name: 'title',
				type: 'string',
				default: '',
			},
		],
	},
	{
		displayName: 'Deeplinks',
		name: 'deeplink',
		placeholder: 'Add Deep Link',
		type: 'fixedCollection',
		typeOptions: {
			multipleValues: true,
		},
		displayOptions: {
			show: {
				resource: ['link'],
				operation: ['create'],
			},
		},
		default: {},
		options: [
			{
				name: 'deeplinkUi',
				displayName: 'Deep Link',
				values: [
					{
						displayName: 'App ID',
						name: 'appId',
						type: 'string',
						default: '',
					},
					{
						displayName: 'App URI Path',
						name: 'appUriPath',
						type: 'string',
						default: '',
					},
					{
						displayName: 'Install Type',
						name: 'installType',
						type: 'string',
						default: '',
					},
					{
						displayName: 'Install URL',
						name: 'installUrl',
						type: 'string',
						default: '',
					},
				],
			},
		],
	},
	/* -------------------------------------------------------------------------- */
	/*                                link:update                                 */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Bitlink',
		name: 'id',
		type: 'string',
		default: '',
		placeholder: 'bit.ly/22u3ypK',
		required: true,
		displayOptions: {
			show: {
				resource: ['link'],
				operation: ['update'],
			},
		},
	},
	{
		displayName: 'Update Fields',
		name: 'updateFields',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				resource: ['link'],
				operation: ['update'],
			},
		},
		options: [
			{
				displayName: 'Archived',
				name: 'archived',
				type: 'boolean',
				default: false,
			},
			{
				displayName: 'Group Name or ID',
				name: 'group',
				type: 'options',
				description:
					'Choose from the list, or specify an ID using an expression',
				default: '',
				typeOptions: {
					loadOptionsMethod: 'getGroups',
				},
			},
			{
				displayName: 'Long URL',
				name: 'longUrl',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Tag Names or IDs',
				name: 'tags',
				type: 'multiOptions',
				description:
					'Choose from the list, or specify IDs using an expression',
				default: [],
				typeOptions: {
					loadOptionsMethod: 'getTags',
					loadOptionsDependsOn: ['group'],
				},
			},
			{
				displayName: 'Title',
				name: 'title',
				type: 'string',
				default: '',
			},
		],
	},
	{
		displayName: 'Deeplinks',
		name: 'deeplink',
		placeholder: 'Add Deep Link',
		type: 'fixedCollection',
		typeOptions: {
			multipleValues: true,
		},
		displayOptions: {
			show: {
				resource: ['link'],
				operation: ['update'],
			},
		},
		default: {},
		options: [
			{
				name: 'deeplinkUi',
				displayName: 'Deep Link',
				values: [
					{
						displayName: 'App ID',
						name: 'appId',
						type: 'string',
						default: '',
					},
					{
						displayName: 'App URI Path',
						name: 'appUriPath',
						type: 'string',
						default: '',
					},
					{
						displayName: 'Install Type',
						name: 'installType',
						type: 'string',
						default: '',
					},
					{
						displayName: 'Install URL',
						name: 'installUrl',
						type: 'string',
						default: '',
					},
				],
			},
		],
	},
	/* -------------------------------------------------------------------------- */
	/*                                 link:get                                   */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Bitlink',
		name: 'id',
		type: 'string',
		default: '',
		placeholder: 'bit.ly/22u3ypK',
		required: true,
		displayOptions: {
			show: {
				resource: ['link'],
				operation: ['get'],
			},
		},
	},
];


import { getAlert as execute } from './execute';
import { rmmGetDescription as description } from './description';

export {
	description,
	execute,
};


export const NODE_PACKAGE_PREFIX = 'n8n-nodes-';

export const RESPONSE_ERROR_MESSAGES = {

export const STARTER_TEMPLATE_NAME = `${NODE_PACKAGE_PREFIX}starter`;


/* eslint-disable @typescript-eslint/no-invalid-void-type */

import express from 'express';

import { AuthenticatedRequest, PaginatatedRequest } from '../../../types';
import { decodeCursor } from '../services/pagination.service';

export const authorize =
	(authorizedRoles: readonly string[]) =>
	(
		req: AuthenticatedRequest,
		res: express.Response,
		next: express.NextFunction,
	): express.Response | void => {
		const { name } = req.user.globalRole;

		if (!authorizedRoles.includes(name)) {
			return res.status(403).json({ message: 'Forbidden' });
		}

		return next();
	};

export const validCursor = (
	req: PaginatatedRequest,
	res: express.Response,
	next: express.NextFunction,
): express.Response | void => {
	if (req.query.cursor) {
		const { cursor } = req.query;
		try {
			const paginationData = decodeCursor(cursor);
			if ('offset' in paginationData) {
				req.query.offset = paginationData.offset;
				req.query.limit = paginationData.limit;
			} else {
				req.query.lastId = paginationData.lastId;
				req.query.limit = paginationData.limit;
			}
		} catch (error) {
			return res.status(400).json({
				message: 'An invalid cursor was provided',
			});
		}
	}

	return next();
};


import { ICredentialType, INodeProperties } from 'n8n-workflow';

export class PhilipsHueOAuth2Api implements ICredentialType {
	name = 'philipsHueOAuth2Api';
	extends = ['oAuth2Api'];
	displayName = 'PhilipHue OAuth2 API';
	documentationUrl = 'philipsHue';
	properties: INodeProperties[] = [
		{
			displayName: 'Grant Type',
			name: 'grantType',
			type: 'hidden',
			default: 'authorizationCode',
		},
		{
			displayName: 'APP ID',
			name: 'appId',
			type: 'string',
			default: '',
		},
		{
			displayName: 'Authorization URL',
			name: 'authUrl',
			type: 'hidden',
			default: 'https://api.meethue.com/v2/oauth2/authorize',
		},
		{
			displayName: 'Access Token URL',
			name: 'accessTokenUrl',
			type: 'hidden',
			default: 'https://api.meethue.com/v2/oauth2/token',
		},
		{
			displayName: 'Auth URI Query Parameters',
			name: 'authQueryParameters',
			type: 'hidden',
			default: '={{"appid="+$self["appId"]}}',
		},
		{
			displayName: 'Scope',
			name: 'scope',
			type: 'hidden',
			default: '',
		},
		{
			displayName: 'Authentication',
			name: 'authentication',
			type: 'hidden',
			default: 'header',
		},
	];
}


<template>

			<n8n-tooltip placement="top" :disabled="!showTooltip">
				<template #content>{{ nodeTypeName }}</template>

					{{ nodeTypeName? nodeTypeName.charAt(0) : '?' }}
					?

			</n8n-tooltip>

</template>


keep keep keep replace keep keep keep keep

const { iconFileData, name, author, version, iconFileContent, installed, isSubscribed, modifiedAt, bundledIn } = app;

import { Box } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import moment from 'moment';
import React, { ReactElement } from 'react';

import AppAvatar from '../../../components/avatar/AppAvatar';
import AppMenu from './AppMenu';
import AppStatus from './AppStatus';
import BundleChips from './BundleChips';
import { App } from './types';

const AppDetailsHeader = ({ app }: { app: App }): ReactElement => {
	const t = useTranslation();
	const { iconFileData, name, author, version, iconFileContent, installed, isSubscribed, modifiedAt, bundledIn, description } = app;
	const lastUpdated = modifiedAt && moment(modifiedAt).fromNow();

	return (
		<Box display='flex' flexDirection='row' mbe='x20' w='full'>
			<AppAvatar size='x124' mie='x20' iconFileContent={iconFileContent} iconFileData={iconFileData} />
			<Box display='flex' flexDirection='column'>
				<Box display='flex' flexDirection='row' alignItems='center' mbe='x8'>
					<Box fontScale='h1' mie='x8'>
						{name}
					</Box>
					{bundledIn && Boolean(bundledIn.length) && <BundleChips bundledIn={bundledIn} />}
				</Box>
				<Box mbe='x16'>{description}</Box>
				<Box display='flex' flexDirection='row' alignItems='center' mbe='x16'>
					<Box display='flex' flexDirection='row' alignItems='center'>
						<AppStatus app={app} installed={installed} isAppDetailsPage={true} isSubscribed={isSubscribed} />
					</Box>
					{(installed || isSubscribed) && <AppMenu app={app} mis='x8' />}
				</Box>
				<Box display='flex' flexDirection='row' color='hint' alignItems='center'>
					<Box fontScale='p2m' mie='x16'>
						{t('By_author', { author: author?.name })}
					</Box>
					<Box is='span'> | </Box>
					<Box mi='x16'>{t('Version_version', { version })}</Box>
					{lastUpdated && (
						<>
							<Box is='span'> | </Box>
							<Box mis='x16'>
								{t('Marketplace_app_last_updated', {
									lastUpdated,
								})}
							</Box>
						</>
					)}
				</Box>
			</Box>
		</Box>
	);
};

export default AppDetailsHeader;


import { Meteor } from 'meteor/meteor';
import { TAPi18n } from 'meteor/rocketchat:tap-i18n';

import { settings } from '../../settings';
import { ChatMessage, Subscriptions } from '../../models/client';
import { dispatchToastMessage } from '../../../client/lib/toast';

Meteor.methods({
	pinMessage(message) {
		if (!Meteor.userId()) {
			dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-not-authorized') });
			return false;
		}
		if (!settings.get('Message_AllowPinning')) {
			dispatchToastMessage({ type: 'error', message: TAPi18n.__('pinning-not-allowed') });
			return false;
		}
		if (Subscriptions.findOne({ rid: message.rid }) == null) {
			dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-pinning-message') });
			return false;
		}
		if (typeof message._id !== 'string') {
			dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-pinning-message') });
			return false;
		}
		dispatchToastMessage({ type: 'success', message: TAPi18n.__('Message_has_been_pinned') });
		return ChatMessage.update(
			{
				_id: message._id,
				rid: message.rid,
			},
			{
				$set: {
					pinned: true,
				},
			},
		);
	},
	unpinMessage(message) {
		if (!Meteor.userId()) {
			dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-not-authorized') });
			return false;
		}
		if (!settings.get('Message_AllowPinning')) {
			dispatchToastMessage({ type: 'error', message: TAPi18n.__('unpinning-not-allowed') });
			return false;
		}
		if (Subscriptions.findOne({ rid: message.rid }) == null) {
			dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-unpinning-message') });
			return false;
		}
		if (typeof message._id !== 'string') {
			dispatchToastMessage({ type: 'error', message: TAPi18n.__('error-unpinning-message') });
			return false;
		}
		dispatchToastMessage({ type: 'success', message: TAPi18n.__('Message_has_been_unpinned') });
		return ChatMessage.update(
			{
				_id: message._id,
				rid: message.rid,
			},
			{
				$set: {
					pinned: false,
				},
			},
		);
	},
});


import { useCallClient, useIsVoipEnterprise } from '../../../client/contexts/CallContext';
import { EEVoipClient } from '../lib/voip/EEVoipClient';

export const useOutboundDialer = (): EEVoipClient | null => {
	const voipClient = useCallClient();
	const isEnterprise = useIsVoipEnterprise();
	const isOutboundClient = voipClient instanceof EEVoipClient;

	return isEnterprise && isOutboundClient ? voipClient : null;
};


{bundledIn && Boolean(bundledIn.length) && <BundleChips bundledIn={bundledIn} />}

<Box display='flex' flexDirection='row' alignItems='center' mbe='x16'>

<Box display='flex' flexDirection='row' alignItems='center'>

<AppStatus app={app} installed={installed} isAppDetailsPage={true} isSubscribed={isSubscribed} />

keep keep keep keep keep replace keep keep keep keep

{app?.shortDescription && <Box mbe='x16'>{app.shortDescription}</Box>}


/* eslint-env mocha */

import { expect } from 'chai';

import { getCredentials, api, request, credentials } from '../../../data/api-data';
import { saveTags } from '../../../data/livechat/tags';
import { updatePermission, updateSetting } from '../../../data/permissions.helper';
import { IS_EE } from '../../../e2e/config/constants';

(IS_EE ? describe : describe.skip)('[EE] Livechat - Tags', function () {
	this.retries(0);

	before((done) => getCredentials(done));

	before((done) => {
		updateSetting('Livechat_enabled', true).then(done);
	});

	describe('livechat/tags', () => {
		it('should throw unauthorized error when the user does not have the necessary permission', async () => {
			await updatePermission('manage-livechat-tags', []);
			await updatePermission('view-l-room', []);
			const response = await request.get(api('livechat/tags')).set(credentials).expect('Content-Type', 'application/json').expect(403);
			expect(response.body).to.have.property('success', false);
		});
		it('should return an array of tags', async () => {
			await updatePermission('manage-livechat-tags', ['admin']);
			await updatePermission('view-l-room', ['livechat-agent']);
			const tag = await saveTags();
			const response = await request
				.get(api('livechat/tags'))
				.set(credentials)
				.query({ text: tag.name })
				.expect('Content-Type', 'application/json')
				.expect(200);
			expect(response.body).to.have.property('success', true);
			expect(response.body.tags).to.be.an('array').with.lengthOf.greaterThan(0);
			expect(response.body.tags[0]).to.have.property('_id', tag._id);
		});
	});

	describe('livechat/tags/:tagId', () => {
		it('should throw unauthorized error when the user does not have the necessary permission', async () => {
			await updatePermission('manage-livechat-tags', []);
			await updatePermission('view-l-room', []);
			const response = await request.get(api('livechat/tags/123')).set(credentials).expect('Content-Type', 'application/json').expect(403);
			expect(response.body).to.have.property('success', false);
		});
		it('should return null when the tag does not exist', async () => {
			await updatePermission('manage-livechat-tags', ['admin']);
			await updatePermission('view-l-room', ['livechat-agent']);
			const response = await request.get(api('livechat/tags/123')).set(credentials).expect('Content-Type', 'application/json').expect(200);
			expect(response.body.body).to.be.null;
		});
		it('should return a tag', async () => {
			const tag = await saveTags();
			const response = await request
				.get(api(`livechat/tags/${tag._id}`))
				.set(credentials)
				.expect('Content-Type', 'application/json')
				.expect(200);
			expect(response.body).to.have.property('success', true);
			expect(response.body).to.have.property('_id', tag._id);
			expect(response.body).to.have.property('name', tag.name);
			expect(response.body).to.have.property('numDepartments', 0);
		});
	});
});


import { Meteor } from 'meteor/meteor';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import s from 'underscore.string';
import mem from 'mem';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import { Rooms, Users } from '@rocket.chat/models';

import { hasPermission } from '../../app/authorization/server';
import { Subscriptions } from '../../app/models/server';
import { settings } from '../../app/settings/server';
import { getFederationDomain } from '../../app/federation/server/lib/getFederationDomain';
import { isFederationEnabled } from '../../app/federation/server/lib/isFederationEnabled';
import { federationSearchUsers } from '../../app/federation/server/handler';
import { Team } from '../sdk';

const sortChannels = function (field, direction) {
	switch (field) {
		case 'createdAt':
			return {
				ts: direction === 'asc' ? 1 : -1,
			};
		case 'lastMessage':
			return {
				'lastMessage.ts': direction === 'asc' ? 1 : -1,
			};
		default:
			return {
				[field]: direction === 'asc' ? 1 : -1,
			};
	}
};

const sortUsers = function (field, direction) {
	switch (field) {
		case 'email':
			return {
				'emails.address': direction === 'asc' ? 1 : -1,
				'username': direction === 'asc' ? 1 : -1,
			};
		default:
			return {
				[field]: direction === 'asc' ? 1 : -1,
			};
	}
};

async function getChannelsAndGroups(user, canViewAnon, searchTerm, sort, pagination) {
	if ((!user && !canViewAnon) || (user && !hasPermission(user._id, 'view-c-room'))) {
		return;
	}

	const teams = await Team.getAllPublicTeams();
	const publicTeamIds = teams.map(({ _id }) => _id);

	const userTeamsIds = (await Team.listTeamsBySubscriberUserId(user._id, { projection: { teamId: 1 } }))?.map(({ teamId }) => teamId) || [];
	const userRooms = user.__rooms;

	const { cursor, totalCount } = Rooms.findPaginatedByNameOrFNameAndRoomIdsIncludingTeamRooms(
		searchTerm ? new RegExp(searchTerm, 'i') : null,
		[...userTeamsIds, ...publicTeamIds],
		userRooms,
		{
			...pagination,
			sort: {
				featured: -1,
				...sort,
			},
			projection: {
				t: 1,
				description: 1,
				topic: 1,
				name: 1,
				fname: 1,
				lastMessage: 1,
				ts: 1,
				archived: 1,
				default: 1,
				featured: 1,
				usersCount: 1,
				prid: 1,
				teamId: 1,
			},
		},
	);

	const [result, total] = await Promise.all([cursor.toArray(), totalCount]);

	const teamIds = result.filter(({ teamId }) => teamId).map(({ teamId }) => teamId);
	const teamsMains = await Team.listByIds([...new Set(teamIds)], { projection: { _id: 1, name: 1 } });

	const results = result.map((room) => {
		if (room.teamId) {
			const team = teamsMains.find((mainRoom) => mainRoom._id === room.teamId);
			if (team) {
				room.belongsTo = team.name;
			}
		}
		return room;
	});

	return {
		total,
		results,
	};
}

const getChannelsCountForTeam = mem((teamId) => Rooms.findByTeamId(teamId, { projection: { _id: 1 } }).count(), {
	maxAge: 2000,
});

async function getTeams(user, searchTerm, sort, pagination) {
	if (!user) {
		return;
	}

	const userSubs = Subscriptions.cachedFindByUserId(user._id).fetch();
	const ids = userSubs.map((sub) => sub.rid);
	const { cursor, totalCount } = Rooms.findPaginatedContainingNameOrFNameInIdsAsTeamMain(
		searchTerm ? new RegExp(searchTerm, 'i') : null,
		ids,
		{
			...pagination,
			sort: {
				featured: -1,
				...sort,
			},
			projection: {
				t: 1,
				description: 1,
				topic: 1,
				name: 1,
				fname: 1,
				lastMessage: 1,
				ts: 1,
				archived: 1,
				default: 1,
				featured: 1,
				usersCount: 1,
				prid: 1,
				teamId: 1,
				teamMain: 1,
			},
		},
	);
	const results = await Promise.all(
		(
			await cursor.toArray()
		).map(async (room) => ({
			...room,
			roomsCount: await getChannelsCountForTeam(room.teamId),
		})),
	);

	return {
		total: await totalCount,
		results,
	};
}

async function findUsers({ text, sort, pagination, workspace, viewFullOtherUserInfo }) {
	const searchFields =
		workspace === 'all' ? ['username', 'name', 'emails.address'] : settings.get('Accounts_SearchFields').trim().split(',');

	const options = {
		...pagination,
		sort,
		projection: {
			username: 1,
			name: 1,
			nickname: 1,
			bio: 1,
			createdAt: 1,
			...(viewFullOtherUserInfo && { emails: 1 }),
			federation: 1,
			avatarETag: 1,
		},
	};

	if (workspace === 'all') {
		const { cursor, totalCount } = Users.findPaginatedByActiveUsersExcept(text, [], options, searchFields);
		const [results, total] = await Promise.all([cursor.toArray(), totalCount]);
		return {
			total,
			results,
		};
	}

	if (workspace === 'external') {
		const { cursor, totalCount } = Users.findPaginatedByActiveExternalUsersExcept(text, [], options, searchFields, getFederationDomain());
		const [results, total] = await Promise.all([cursor.toArray(), totalCount]);
		return {
			total,
			results,
		};
	}

	const { cursor, totalCount } = Users.findPaginatedByActiveLocalUsersExcept(text, [], options, searchFields, getFederationDomain());
	const [results, total] = await Promise.all([cursor.toArray(), totalCount]);
	return {
		total,
		results,
	};
}

const getUsers = async (user, text, workspace, sort, pagination) => {
	if (!user || !hasPermission(user._id, 'view-outside-room') || !hasPermission(user._id, 'view-d-room')) {
		return;
	}

	const viewFullOtherUserInfo = hasPermission(user._id, 'view-full-other-user-info');

	const { total, results } = await findUsers({ text, sort, pagination, workspace, viewFullOtherUserInfo });

	// Try to find federated users, when applicable
	if (isFederationEnabled() && workspace === 'external' && text.indexOf('@') !== -1) {
		const users = await federationSearchUsers(text);

		for (const user of users) {
			if (results.find((e) => e._id === user._id)) {
				continue;
			}

			// Add the federated user to the results
			results.unshift({
				username: user.username,
				name: user.name,
				bio: user.bio,
				nickname: user.nickname,
				emails: user.emails,
				federation: user.federation,
				isRemote: true,
			});
		}
	}

	return {
		total,
		results,
	};
};

Meteor.methods({
	async browseChannels({ text = '', workspace = '', type = 'channels', sortBy = 'name', sortDirection = 'asc', page, offset, limit = 10 }) {
		const searchTerm = s.trim(escapeRegExp(text));

		if (
			!['channels', 'users', 'teams'].includes(type) ||
			!['asc', 'desc'].includes(sortDirection) ||
			(!page && page !== 0 && !offset && offset !== 0)
		) {
			return;
		}

		const roomParams = ['channels', 'teams'].includes(type) ? ['usernames', 'lastMessage'] : [];
		const userParams = type === 'users' ? ['username', 'email', 'bio'] : [];

		if (!['name', 'createdAt', 'usersCount', ...roomParams, ...userParams].includes(sortBy)) {
			return;
		}

		const skip = Math.max(0, offset || (page > -1 ? limit * page : 0));

		limit = limit > 0 ? limit : 10;

		const pagination = {
			skip,
			limit,
		};

		const canViewAnonymous = !!settings.get('Accounts_AllowAnonymousRead');

		const user = Meteor.user();

		switch (type) {
			case 'channels':
				return getChannelsAndGroups(user, canViewAnonymous, searchTerm, sortChannels(sortBy, sortDirection), pagination);
			case 'teams':
				return getTeams(user, searchTerm, sortChannels(sortBy, sortDirection), pagination);
			case 'users':
				return getUsers(user, text, workspace, sortUsers(sortBy, sortDirection), pagination);
			default:
		}
	},
});

DDPRateLimiter.addRule(
	{
		type: 'method',
		name: 'browseChannels',
		userId(/* userId*/) {
			return true;
		},
	},
	100,
	100000,
);


//! moment.js locale configuration
//! locale : Icelandic [is]
//! author : Hinrik Örn Sigurðsson : https://github.com/hinrik

;(function (global, factory) {
   typeof exports === 'object' && typeof module !== 'undefined'
       && typeof require === 'function' ? factory(require('../moment')) :
   typeof define === 'function' && define.amd ? define(['../moment'], factory) :
   factory(global.moment)
}(this, function (moment) { 'use strict';

    function plural(n) {
        if (n % 100 === 11) {
            return true;
        } else if (n % 10 === 1) {
            return false;
        }
        return true;
    }
    function translate(number, withoutSuffix, key, isFuture) {
        var result = number + ' ';
        switch (key) {
            case 's':
                return withoutSuffix || isFuture ? 'nokkrar sekúndur' : 'nokkrum sekúndum';
            case 'm':
                return withoutSuffix ? 'mínúta' : 'mínútu';
            case 'mm':
                if (plural(number)) {
                    return result + (withoutSuffix || isFuture ? 'mínútur' : 'mínútum');
                } else if (withoutSuffix) {
                    return result + 'mínúta';
                }
                return result + 'mínútu';
            case 'hh':
                if (plural(number)) {
                    return result + (withoutSuffix || isFuture ? 'klukkustundir' : 'klukkustundum');
                }
                return result + 'klukkustund';
            case 'd':
                if (withoutSuffix) {
                    return 'dagur';
                }
                return isFuture ? 'dag' : 'degi';
            case 'dd':
                if (plural(number)) {
                    if (withoutSuffix) {
                        return result + 'dagar';
                    }
                    return result + (isFuture ? 'daga' : 'dögum');
                } else if (withoutSuffix) {
                    return result + 'dagur';
                }
                return result + (isFuture ? 'dag' : 'degi');
            case 'M':
                if (withoutSuffix) {
                    return 'mánuður';
                }
                return isFuture ? 'mánuð' : 'mánuði';
            case 'MM':
                if (plural(number)) {
                    if (withoutSuffix) {
                        return result + 'mánuðir';
                    }
                    return result + (isFuture ? 'mánuði' : 'mánuðum');
                } else if (withoutSuffix) {
                    return result + 'mánuður';
                }
                return result + (isFuture ? 'mánuð' : 'mánuði');
            case 'y':
                return withoutSuffix || isFuture ? 'ár' : 'ári';
            case 'yy':
                if (plural(number)) {
                    return result + (withoutSuffix || isFuture ? 'ár' : 'árum');
                }
                return result + (withoutSuffix || isFuture ? 'ár' : 'ári');
        }
    }

    var is = moment.defineLocale('is', {
        months : 'janúar_febrúar_mars_apríl_maí_júní_júlí_ágúst_september_október_nóvember_desember'.split('_'),
        monthsShort : 'jan_feb_mar_apr_maí_jún_júl_ágú_sep_okt_nóv_des'.split('_'),
        weekdays : 'sunnudagur_mánudagur_þriðjudagur_miðvikudagur_fimmtudagur_föstudagur_laugardagur'.split('_'),
        weekdaysShort : 'sun_mán_þri_mið_fim_fös_lau'.split('_'),
        weekdaysMin : 'Su_Má_Þr_Mi_Fi_Fö_La'.split('_'),
        longDateFormat : {
            LT : 'H:mm',
            LTS : 'H:mm:ss',
            L : 'DD.MM.YYYY',
            LL : 'D. MMMM YYYY',
            LLL : 'D. MMMM YYYY [kl.] H:mm',
            LLLL : 'dddd, D. MMMM YYYY [kl.] H:mm'
        },
        calendar : {
            sameDay : '[í dag kl.] LT',
            nextDay : '[á morgun kl.] LT',
            nextWeek : 'dddd [kl.] LT',
            lastDay : '[í gær kl.] LT',
            lastWeek : '[síðasta] dddd [kl.] LT',
            sameElse : 'L'
        },
        relativeTime : {
            future : 'eftir %s',
            past : 'fyrir %s síðan',
            s : translate,
            m : translate,
            mm : translate,
            h : 'klukkustund',
            hh : translate,
            d : translate,
            dd : translate,
            M : translate,
            MM : translate,
            y : translate,
            yy : translate
        },
        ordinalParse: /\d{1,2}\./,
        ordinal : '%d.',
        week : {
            dow : 1, // Monday is the first day of the week.
            doy : 4  // The week that contains Jan 4th is the first week of the year.
        }
    });

    return is;

}));


homepage: string;
support: string;
};
description: string;
privacyPolicySummary: string;
detailedDescription: {
raw: string;
rendered: string;


Nom

Voulez-vous vraiment terminer cette conversation ?

Voulez-vous vraiment supprimer toutes vos données personnelles ?

Êtes-vous sûr de vouloir changer de département ?

Un nouveau message depuis {{val, datetime}}

{{count}} nouveaux messages depuis {{val, datetime}}

Erreur lors de la fermeture de la conversation.

Erreur lors de la suppression des données utilisateur.

Erreur lors de la création d'une nouvelle conversation : {{reason}}

Le fichier dépasse la taille autorisée de {{size}}.

Ouvrir <1>menu options → Oublier/Supprimer mes données personnelles</1> pour demander la suppression immédiate de vos données personnelles.

Si vous avez une autre question, cliquez sur le bouton ci-dessous pour démarrer une nouvelle conversation.

Aucun agent disponible pour un transfert

Veuillez nous donner quelques informations pour démarrer la conversation

Veuillez patienter, attente d'un agent disponible...

L'opérateur de vos données personnelles est [Company Name], dont l'adresse est [Company Address]. Pour débuter le chat vous acceptez que vos données personnelles soient utilisées et transmises conformément au règlement général sur la protection des données (RGPD).

Nous ne sommes pas en ligne en ce moment. Merci de laisser un message.

Votre navigateur ne supporte pas les éléments audio

Votre navigateur ne supporte pas les éléments vidéo

Votre rang est #{{spot}} (Temps d'attente estimé: {{estimatedWaitTime}})


import type { Root } from '@rocket.chat/message-parser';

import type { MessageAttachmentBase } from './MessageAttachmentBase';

export type MessageQuoteAttachment = {
	author_name: string;
	author_link: string;
	author_icon: string;
	message_link?: string;
	text: string;
	md: Root;
	attachments?: Array<MessageQuoteAttachment>; // TODO this is cauising issues to define a model, see @ts-expect-error at apps/meteor/app/api/server/v1/channels.ts:274
} & MessageAttachmentBase;

export const isQuoteAttachment = (attachment: MessageAttachmentBase): attachment is MessageQuoteAttachment => 'message_link' in attachment;


on: Object.assign({}, { click: this.click }, this.$listeners),

keep keep keep keep keep replace keep keep keep

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VListGroup.js should render a component with no padding for action icon and match snapshot 1`] = `

`;

exports[`VListGroup.js should render a component with route namespace and match snapshot 1`] = `

`;

exports[`VListGroup.js should render a lazy component and match snapshot 1`] = `

`;

exports[`VListGroup.js should render component and match snapshot 1`] = `

`;


import VCarousel from './VCarousel'
import VCarouselItem from './VCarouselItem'
import { test } from '~util/testing'
import Vue from 'vue'

const create = (props = {}, slots = 3) => Vue.component('zxc', {
  functional: true,
  render (h) {
    const items = []
    for (let i = 0; i < slots; i++) {
      items.push(h(VCarouselItem, { props: { src: `${i + 1}` } }))
    }
    return h(VCarousel, { props }, items)
  }
})

test('VCarousel.js', ({ mount }) => {
  it('should render component and match snapshot', async () => {
    const wrapper = mount(VCarousel)
    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with image cycling off and match snapshot', async () => {
    const wrapper = mount(VCarousel, {
      propsData: {
        cycle: false
      }
    })
    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with custom icon and match snapshot', async () => {
    const component = create({
      delimiterIcon: 'stop'
    })
    const wrapper = mount(component)
    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with custom duration between image cycles and match snapshot', async () => {
    const wrapper = mount(VCarousel, {
      propsData: {
        interval: 1000
      }
    })
    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with custom left control icon and match snapshot', async () => {
    const wrapper = mount(VCarousel, {
      propsData: {
        leftControlIcon: 'stop'
      }
    })
    await wrapper.vm.$nextTick()

    expect(wrapper.find('.carousel__left .icon')[0].text()).toBe('stop')
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component without left control icon and match snapshot', async () => {
    const wrapper = mount(VCarousel, {
      propsData: {
        leftControlIcon: false
      }
    })
    await wrapper.vm.$nextTick()

    expect(wrapper.contains('.carousel__left')).toBe(false)
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with custom right control icon and match snapshot', async () => {
    const wrapper = mount(VCarousel, {
      propsData: {
        rightControlIcon: 'stop'
      }
    })
    await wrapper.vm.$nextTick()

    expect(wrapper.find('.carousel__right .icon')[0].text()).toBe('stop')
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component without right control icon and match snapshot', async () => {
    const wrapper = mount(VCarousel, {
      propsData: {
        rightControlIcon: false
      }
    })
    await wrapper.vm.$nextTick()

    expect(wrapper.contains('.carousel__right')).toBe(false)
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with selected active item', async () => {
    const component = create({ value: 1 })
    const wrapper = mount(component)
    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
  })

  // TODO: Use jest's fake timers
  it('should emit input event after interval', async () => {
    const vm = mount(VCarousel).vm
    const wrapper = mount(VCarousel, {
      propsData: {
        value: 1,
        interval: 1
      },
      slots: {
        default: [1, 2, 3].map(i => {
          return {
            vNode: vm.$createElement(VCarouselItem, { attrs: { src: i.toString() } })
          }
        })
      }
    })

    const input = jest.fn()
    await new Promise(resolve => {
      wrapper.vm.$on('input', value => {
        input(value)
        input.mock.calls.length === 3 && resolve()
      })
    })

    expect([].concat(...input.mock.calls)).toEqual([1, 2, 0])
  })

  it('should render component without delimiters', async () => {
    const component = create({ hideDelimiters: true })
    const wrapper = mount(component)
    await wrapper.vm.$nextTick()

    expect(wrapper.contains('carousel__controls')).toBe(false)
    expect(wrapper.html()).toMatchSnapshot()
  })
})


@import '../settings/_elevations'

// GENERATE HELPER CLASSES
for z in (0..24)
  .elevation-{z}
    elevation(z, true)


require('../../stylus/components/_breadcrumbs.styl')

export default {
  name: 'v-breadcrumbs',

  props: {
    divider: {
      type: String,
      default: '/'
    },
    large: Boolean,
    justifyCenter: Boolean,
    justifyEnd: Boolean
  },

  computed: {
    classes () {
      return {
        'breadcrumbs--large': this.large
      }
    },
    computedDivider () {
      return this.$slots.divider
        ? this.$slots.divider
        : this.divider
    },
    styles () {
      const justify = this.justifyCenter
        ? 'center'
        : this.justifyEnd
          ? 'flex-end'
          : 'flex-start'

      return {
        'justify-content': justify
      }
    }
  },

  methods: {
    /**
     * Add dividers between
     * v-breadcrumbs-item
     *
     * @return {array}
     */
    genChildren () {
      if (!this.$slots.default) return null

      const children = []
      const dividerData = { staticClass: 'breadcrumbs__divider' }
      const length = this.$slots.default.length

      this.$slots.default.forEach((elm, i) => {
        children.push(elm)

        if (!elm.componentOptions ||
          elm.componentOptions.tag !== 'v-breadcrumbs-item' ||
          i === length - 1
        ) return

        children.push(this.$createElement('li', dividerData, this.computedDivider))
      })

      return children
    }
  },

  render (h) {
    return h('ul', {
      staticClass: 'breadcrumbs',
      'class': this.classes,
      style: this.styles
    }, this.genChildren())
  }
}


const transition = h(VExpandTransition, [group])

return h('div', { 'class': 'list--group__container' }, [item, transition])

return h('li', { 'class': 'list--group__container' }, [item, transition])