From 6f149019c4306baf566fbcfd16acc207218cbbf9 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Thu, 3 Aug 2023 11:09:46 -0400 Subject: [PATCH 01/63] rename createSubLog to createLoggerInstance --- apps/app/src/pages/api/i18n/load.ts | 4 ++-- apps/app/src/pages/api/trpc/[trpc].ts | 4 ++-- apps/app/src/utils/api.ts | 4 ++-- apps/app/src/utils/vercel-kv.ts | 4 ++-- packages/api/cache/slugRedirect.ts | 4 ++-- packages/api/cache/slugToOrgId.ts | 4 ++-- packages/api/types/handler.ts | 5 ++--- packages/auth/lib/cognitoClient.ts | 4 ++-- packages/auth/lib/logger.ts | 4 ++-- packages/db/client/index.ts | 4 ++-- packages/db/lib/superjsonMiddleware.ts | 4 ++-- packages/util/logger/index.ts | 2 +- 12 files changed, 23 insertions(+), 24 deletions(-) diff --git a/apps/app/src/pages/api/i18n/load.ts b/apps/app/src/pages/api/i18n/load.ts index 455362ee67..dc00cb9731 100644 --- a/apps/app/src/pages/api/i18n/load.ts +++ b/apps/app/src/pages/api/i18n/load.ts @@ -4,7 +4,7 @@ import { context, trace } from '@opentelemetry/api' import { type NextApiRequest, type NextApiResponse } from 'next' import { z } from 'zod' -import { createSubLog } from '@weareinreach/util/logger' +import { createLoggerInstance } from '@weareinreach/util/logger' import { crowdinOpts } from '~app/data/crowdinOta' import { crowdinDistTimestamp, fetchCrowdinDbKey, fetchCrowdinFile } from '~app/utils/crowdin' import { redisReadCache, redisWriteCache } from '~app/utils/vercel-kv' @@ -14,7 +14,7 @@ const QuerySchema = z.object({ ns: z.string(), }) const tracer = trace.getTracer('inreach-app') -const log = createSubLog('i18n Loader') +const log = createLoggerInstance('i18n Loader') export default async function handler(req: NextApiRequest, res: NextApiResponse) { const query = QuerySchema.parse(req.query) diff --git a/apps/app/src/pages/api/trpc/[trpc].ts b/apps/app/src/pages/api/trpc/[trpc].ts index 245adf0361..fc6aac6565 100644 --- a/apps/app/src/pages/api/trpc/[trpc].ts +++ b/apps/app/src/pages/api/trpc/[trpc].ts @@ -1,9 +1,9 @@ import { createNextApiHandler } from '@trpc/server/adapters/next' import { appRouter, createContext } from '@weareinreach/api' -import { createSubLog } from '@weareinreach/util/logger' +import { createLoggerInstance } from '@weareinreach/util/logger' -const log = createSubLog('tRPC') +const log = createLoggerInstance('tRPC') /* Creating a handler for the tRPC endpoint. */ export default createNextApiHandler({ diff --git a/apps/app/src/utils/api.ts b/apps/app/src/utils/api.ts index 33aa198cec..25516a8fc8 100644 --- a/apps/app/src/utils/api.ts +++ b/apps/app/src/utils/api.ts @@ -9,9 +9,9 @@ import { devtoolsLink } from 'trpc-client-devtools-link' import { type AppRouter } from '@weareinreach/api' import { transformer } from '@weareinreach/api/lib/transformer' import { getEnv } from '@weareinreach/env' -import { createSubLog } from '@weareinreach/util/logger' +import { createLoggerInstance } from '@weareinreach/util/logger' -const log = createSubLog('tRPC') +const log = createLoggerInstance('tRPC') const getBaseUrl = () => { if (typeof window !== 'undefined') return '' // browser should use relative url if (getEnv('VERCEL_URL')) return `https://${getEnv('VERCEL_URL')}` // SSR should use vercel url diff --git a/apps/app/src/utils/vercel-kv.ts b/apps/app/src/utils/vercel-kv.ts index 2d11811aec..67a30f3de1 100644 --- a/apps/app/src/utils/vercel-kv.ts +++ b/apps/app/src/utils/vercel-kv.ts @@ -4,10 +4,10 @@ import { flatten, unflatten } from 'flat' import sizeof from 'object-sizeof' import formatBytes from 'pretty-bytes' -import { createSubLog } from '@weareinreach/util/logger' +import { createLoggerInstance } from '@weareinreach/util/logger' const redisTTL = 86400 -const log = createSubLog('Vercel KV') +const log = createLoggerInstance('Vercel KV') const tracer = trace.getTracer('inreach-app') export const redisReadCache = async (namespaces: string[], lang: string, otaManifestTimestamp: number) => { diff --git a/packages/api/cache/slugRedirect.ts b/packages/api/cache/slugRedirect.ts index 9c9df41ed7..3e200da295 100644 --- a/packages/api/cache/slugRedirect.ts +++ b/packages/api/cache/slugRedirect.ts @@ -1,8 +1,8 @@ import { kv as redis } from '@vercel/kv' -import { createSubLog } from '@weareinreach/util/logger' +import { createLoggerInstance } from '@weareinreach/util/logger' -const log = createSubLog('Cache - Slug redirect') +const log = createLoggerInstance('Cache - Slug redirect') export const readSlugRedirectCache = async (slug: string) => { try { diff --git a/packages/api/cache/slugToOrgId.ts b/packages/api/cache/slugToOrgId.ts index 90df7f8a79..5320b8f6aa 100644 --- a/packages/api/cache/slugToOrgId.ts +++ b/packages/api/cache/slugToOrgId.ts @@ -1,8 +1,8 @@ import { kv as redis } from '@vercel/kv' -import { createSubLog } from '@weareinreach/util/logger' +import { createLoggerInstance } from '@weareinreach/util/logger' -const log = createSubLog('Cache - Slug to OrgId') +const log = createLoggerInstance('Cache - Slug to OrgId') export const readSlugCache = async (slug: string) => { try { diff --git a/packages/api/types/handler.ts b/packages/api/types/handler.ts index 91d8ba6562..729cbb43c2 100644 --- a/packages/api/types/handler.ts +++ b/packages/api/types/handler.ts @@ -1,6 +1,5 @@ import { type Context } from '~api/lib/context' -export type TRPCHandlerParams = { +export type TRPCHandlerParams = { ctx: Context - input: T -} +} & (undefined extends TInput ? { input?: never } : { input: TInput }) diff --git a/packages/auth/lib/cognitoClient.ts b/packages/auth/lib/cognitoClient.ts index d28d8bf22b..e89e0e83d6 100644 --- a/packages/auth/lib/cognitoClient.ts +++ b/packages/auth/lib/cognitoClient.ts @@ -13,12 +13,12 @@ import { createHmac } from 'crypto' import { prisma } from '@weareinreach/db' import { getEnv } from '@weareinreach/env' -import { createSubLog } from '@weareinreach/util/logger' +import { createLoggerInstance } from '@weareinreach/util/logger' import { decodeCognitoIdJwt } from './cognitoJwt' import { generateUserSession } from './genUserSession' -const logger = createSubLog('Cognito') +const logger = createLoggerInstance('Cognito') export const CognitoSessionSchema = z.object({ AccessToken: z.string(), diff --git a/packages/auth/lib/logger.ts b/packages/auth/lib/logger.ts index b4027b397e..13e1aa03cb 100644 --- a/packages/auth/lib/logger.ts +++ b/packages/auth/lib/logger.ts @@ -1,3 +1,3 @@ -import { createSubLog } from '@weareinreach/util/logger' +import { createLoggerInstance } from '@weareinreach/util/logger' -export const logger = createSubLog('@weareinreach/auth') +export const logger = createLoggerInstance('@weareinreach/auth') diff --git a/packages/db/client/index.ts b/packages/db/client/index.ts index d3816c0283..6250bc00d1 100644 --- a/packages/db/client/index.ts +++ b/packages/db/client/index.ts @@ -2,11 +2,11 @@ import { type Prisma, PrismaClient } from '@prisma/client' import { createPrismaQueryEventHandler } from 'prisma-query-log' -import { createSubLog } from '@weareinreach/util/logger' +import { createLoggerInstance } from '@weareinreach/util/logger' import { idMiddleware } from '~db/lib/idMiddleware' import { superjsonMiddleware } from '~db/lib/superjsonMiddleware' -const log = createSubLog('prisma') +const log = createLoggerInstance('prisma') const verboseLogging = Boolean( // eslint-disable-next-line turbo/no-undeclared-env-vars process.env.NODE_ENV === 'development' && (!!process.env.NEXT_VERBOSE || !!process.env.PRISMA_VERBOSE) diff --git a/packages/db/lib/superjsonMiddleware.ts b/packages/db/lib/superjsonMiddleware.ts index c82a4108c2..ceebc9212c 100644 --- a/packages/db/lib/superjsonMiddleware.ts +++ b/packages/db/lib/superjsonMiddleware.ts @@ -3,13 +3,13 @@ import superjson from 'superjson' import { type SuperJSONResult } from 'superjson/dist/types' import { z } from 'zod' -import { createSubLog } from '@weareinreach/util/logger' +import { createLoggerInstance } from '@weareinreach/util/logger' import { NullableJsonValue } from './zod' const MODELS_TO_RUN: Prisma.ModelName[] = ['AttributeSupplement', 'Suggestion'] -const logger = createSubLog('SuperJSON middleware', { minLevel: 3 }) +const logger = createLoggerInstance('SuperJSON middleware', { minLevel: 3 }) const ResultSchema = z .object({ diff --git a/packages/util/logger/index.ts b/packages/util/logger/index.ts index 246feb3744..a10b43b2b3 100644 --- a/packages/util/logger/index.ts +++ b/packages/util/logger/index.ts @@ -2,5 +2,5 @@ import { type ISettingsParam, Logger } from 'tslog' export const appLog = new Logger({ name: 'app', type: 'json', hideLogPositionForProduction: true }) -export const createSubLog = (name: string, opts?: Omit, 'name'>) => +export const createLoggerInstance = (name: string, opts?: Omit, 'name'>) => appLog.getSubLogger({ name, ...opts }) From ade14beb1be76babce8538077950d79d488acdd3 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Thu, 3 Aug 2023 12:01:16 -0400 Subject: [PATCH 02/63] lazy load attributes router --- packages/api/router/attribute/index.ts | 30 +++++++++-- packages/api/router/attribute/queries.ts | 52 ------------------- .../query.getFilterOptions.handler.ts | 31 +++++++++++ .../router/attribute/query.getOne.handler.ts | 31 +++++++++++ .../router/attribute/query.getOne.schema.ts | 4 ++ packages/api/router/attribute/schemas.ts | 3 ++ 6 files changed, 95 insertions(+), 56 deletions(-) delete mode 100644 packages/api/router/attribute/queries.ts create mode 100644 packages/api/router/attribute/query.getFilterOptions.handler.ts create mode 100644 packages/api/router/attribute/query.getOne.handler.ts create mode 100644 packages/api/router/attribute/query.getOne.schema.ts create mode 100644 packages/api/router/attribute/schemas.ts diff --git a/packages/api/router/attribute/index.ts b/packages/api/router/attribute/index.ts index 6fc457f1b2..66b3df8366 100644 --- a/packages/api/router/attribute/index.ts +++ b/packages/api/router/attribute/index.ts @@ -1,6 +1,28 @@ -import { mergeRouters } from '~api/lib/trpc' +import { defineRouter, publicProcedure, staffProcedure } from '~api/lib/trpc' -// import { mutations } from './mutations' -import { queries } from './queries' +import * as schema from './schemas' -export const attributeRouter = mergeRouters(queries /*mutations*/) +type AttributeQueryHandlerCache = { + getFilterOptions: typeof import('./query.getFilterOptions.handler').getFilterOptions + getOne: typeof import('./query.getOne.handler').getOne +} + +const HandlerCache: Partial = {} +export const attributeRouter = defineRouter({ + getFilterOptions: publicProcedure.query(async ({ ctx }) => { + if (!HandlerCache.getFilterOptions) + HandlerCache.getFilterOptions = await import('./query.getFilterOptions.handler').then( + (mod) => mod.getFilterOptions + ) + + if (!HandlerCache.getFilterOptions) throw new Error('Failed to load handler') + return HandlerCache.getFilterOptions({ ctx }) + }), + getOne: staffProcedure.input(schema.ZGetOneSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getOne) + HandlerCache.getOne = await import('./query.getOne.handler').then((mod) => mod.getOne) + + if (!HandlerCache.getOne) throw new Error('Failed to load handler') + return HandlerCache.getOne({ ctx, input }) + }), +}) diff --git a/packages/api/router/attribute/queries.ts b/packages/api/router/attribute/queries.ts deleted file mode 100644 index ea7156c9ee..0000000000 --- a/packages/api/router/attribute/queries.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { z } from 'zod' - -import { defineRouter, publicProcedure, staffProcedure } from '~api/lib/trpc' - -export const queries = defineRouter({ - getFilterOptions: publicProcedure.query(async ({ ctx }) => { - const result = await ctx.prisma.attribute.findMany({ - where: { - AND: { - filterType: { - not: null, - }, - active: true, - }, - }, - select: { - id: true, - tsKey: true, - tsNs: true, - filterType: true, - }, - orderBy: { - tsKey: 'asc', - }, - }) - - return result - }), - getOne: staffProcedure - .input(z.object({ id: z.string() }).or(z.object({ tag: z.string() }))) - .query(async ({ ctx, input }) => { - const result = await ctx.prisma.attribute.findUniqueOrThrow({ - where: input, - select: { - id: true, - tag: true, - tsKey: true, - tsNs: true, - icon: true, - iconBg: true, - active: true, - requireBoolean: true, - requireData: true, - requireDataSchema: { select: { definition: true } }, - requireGeo: true, - requireLanguage: true, - requireText: true, - }, - }) - return result - }), -}) diff --git a/packages/api/router/attribute/query.getFilterOptions.handler.ts b/packages/api/router/attribute/query.getFilterOptions.handler.ts new file mode 100644 index 0000000000..2ec3597580 --- /dev/null +++ b/packages/api/router/attribute/query.getFilterOptions.handler.ts @@ -0,0 +1,31 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +export const getFilterOptions = async (ctx: TRPCHandlerParams) => { + try { + const result = await prisma.attribute.findMany({ + where: { + AND: { + filterType: { + not: null, + }, + active: true, + }, + }, + select: { + id: true, + tsKey: true, + tsNs: true, + filterType: true, + }, + orderBy: { + tsKey: 'asc', + }, + }) + + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/attribute/query.getOne.handler.ts b/packages/api/router/attribute/query.getOne.handler.ts new file mode 100644 index 0000000000..28b05bc938 --- /dev/null +++ b/packages/api/router/attribute/query.getOne.handler.ts @@ -0,0 +1,31 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetOneSchema } from './query.getOne.schema' + +export const getOne = async ({ input }: TRPCHandlerParams) => { + try { + const result = await prisma.attribute.findUniqueOrThrow({ + where: input, + select: { + id: true, + tag: true, + tsKey: true, + tsNs: true, + icon: true, + iconBg: true, + active: true, + requireBoolean: true, + requireData: true, + requireDataSchema: { select: { definition: true } }, + requireGeo: true, + requireLanguage: true, + requireText: true, + }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/attribute/query.getOne.schema.ts b/packages/api/router/attribute/query.getOne.schema.ts new file mode 100644 index 0000000000..75d9d84f82 --- /dev/null +++ b/packages/api/router/attribute/query.getOne.schema.ts @@ -0,0 +1,4 @@ +import { z } from 'zod' + +export const ZGetOneSchema = z.object({ id: z.string() }).or(z.object({ tag: z.string() })) +export type TGetOneSchema = z.infer diff --git a/packages/api/router/attribute/schemas.ts b/packages/api/router/attribute/schemas.ts new file mode 100644 index 0000000000..a19e03c263 --- /dev/null +++ b/packages/api/router/attribute/schemas.ts @@ -0,0 +1,3 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './query.getOne.schema' +// codegen:end From 99ecec480c703d31c73c7913d1eb965ffdc456b2 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Thu, 3 Aug 2023 12:02:02 -0400 Subject: [PATCH 03/63] allow import() --- packages/eslint-config/base.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/eslint-config/base.js b/packages/eslint-config/base.js index a4a24d97bc..fdcd0ccf95 100644 --- a/packages/eslint-config/base.js +++ b/packages/eslint-config/base.js @@ -17,6 +17,7 @@ const config = { { prefer: 'type-imports', fixStyle: 'inline-type-imports', + disallowTypeAnnotations: false, }, ], 'no-unused-vars': 'off', From d2af2730e6400eb39d7a9c7139d086169b38fef9 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Thu, 3 Aug 2023 13:00:56 -0400 Subject: [PATCH 04/63] update unused-vars --- packages/eslint-config/base.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-config/base.js b/packages/eslint-config/base.js index fdcd0ccf95..77928b322c 100644 --- a/packages/eslint-config/base.js +++ b/packages/eslint-config/base.js @@ -25,8 +25,9 @@ const config = { 'warn', { varsIgnorePattern: '^_', - args: 'none', + args: 'after-used', ignoreRestSiblings: true, + destructuredArrayIgnorePattern: '^_', }, ], '@typescript-eslint/no-empty-function': 'off', From 3fad5ce0928965dc5bba9616068e1c8ca98d0e10 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:00:26 -0400 Subject: [PATCH 05/63] prefixed id schema/generator --- packages/api/router/organization/query.getById.schema.ts | 4 +++- packages/api/schemas/idPrefix.ts | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 packages/api/schemas/idPrefix.ts diff --git a/packages/api/router/organization/query.getById.schema.ts b/packages/api/router/organization/query.getById.schema.ts index c98c99eff2..b86c24fea3 100644 --- a/packages/api/router/organization/query.getById.schema.ts +++ b/packages/api/router/organization/query.getById.schema.ts @@ -1,4 +1,6 @@ import { z } from 'zod' -export const ZGetByIdSchema = z.object({ id: z.string() }) +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetByIdSchema = z.object({ id: prefixedId('organization') }) export type TGetByIdSchema = z.infer diff --git a/packages/api/schemas/idPrefix.ts b/packages/api/schemas/idPrefix.ts new file mode 100644 index 0000000000..da27f13186 --- /dev/null +++ b/packages/api/schemas/idPrefix.ts @@ -0,0 +1,5 @@ +import { z } from 'zod' + +import { type IdPrefix, idPrefix } from '@weareinreach/db/lib/idGen' + +export const prefixedId = (model: IdPrefix) => z.string().regex(new RegExp(`^${idPrefix[model]}_\\w+$`)) From 19569dac04133119913b4a2e1edebddb8762909a Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Fri, 4 Aug 2023 19:00:17 -0400 Subject: [PATCH 06/63] update @storybook/addon-designs --- packages/ui/.storybook/main.ts | 2 +- packages/ui/package.json | 3 +- pnpm-lock.yaml | 249 ++++----------------------------- 3 files changed, 33 insertions(+), 221 deletions(-) diff --git a/packages/ui/.storybook/main.ts b/packages/ui/.storybook/main.ts index 2cb2cb369b..853fc10421 100644 --- a/packages/ui/.storybook/main.ts +++ b/packages/ui/.storybook/main.ts @@ -27,7 +27,7 @@ const config: StorybookConfig = { '@storybook/addon-a11y', '@storybook/addon-interactions', '@tomfreudenberg/next-auth-mock/storybook', - 'storybook-addon-designs', + '@storybook/addon-designs', 'storybook-addon-pseudo-states', // 'css-chaos-addon', 'storybook-addon-swc', diff --git a/packages/ui/package.json b/packages/ui/package.json index d38b282e2d..f33dbab3f3 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -76,6 +76,7 @@ "@mantine/utils": "6.0.17", "@storybook/addon-a11y": "7.2.0", "@storybook/addon-actions": "7.2.0", + "@storybook/addon-designs": "7.0.1", "@storybook/addon-docs": "7.2.0", "@storybook/addon-essentials": "7.2.0", "@storybook/addon-interactions": "7.2.0", @@ -121,7 +122,6 @@ "@weareinreach/eslint-config": "0.100.0", "babel-loader": "9.1.3", "chromatic": "6.20.0", - "css-chaos-addon": "0.0.5", "css-loader": "6.8.1", "dayjs": "1.11.9", "dotenv": "16.3.1", @@ -149,7 +149,6 @@ "resolve-url-loader": "5.0.0", "slugify": "1.6.6", "storybook": "7.2.0", - "storybook-addon-designs": "7.0.0-beta.2", "storybook-addon-pseudo-states": "2.1.0", "storybook-addon-swc": "1.2.0", "storybook-addon-turbo-build": "2.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d99bfa268..560fbe4b69 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1524,6 +1524,9 @@ importers: '@storybook/addon-actions': specifier: 7.2.0 version: 7.2.0(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-designs': + specifier: 7.0.1 + version: 7.0.1(@storybook/addon-docs@7.2.0)(@storybook/addons@7.2.0)(@storybook/components@7.2.0)(@storybook/manager-api@7.2.0)(@storybook/preview-api@7.2.0)(@storybook/theming@7.2.0)(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-docs': specifier: 7.2.0 version: 7.2.0(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) @@ -1659,9 +1662,6 @@ importers: chromatic: specifier: 6.20.0 version: 6.20.0 - css-chaos-addon: - specifier: 0.0.5 - version: 0.0.5(@storybook/addons@6.5.16)(@storybook/api@6.5.16)(@storybook/components@7.2.0)(@storybook/core-events@7.2.0)(@storybook/theming@7.2.0)(react-dom@18.2.0)(react@18.2.0) css-loader: specifier: 6.8.1 version: 6.8.1(webpack@5.88.2) @@ -1743,9 +1743,6 @@ importers: storybook: specifier: 7.2.0 version: 7.2.0 - storybook-addon-designs: - specifier: 7.0.0-beta.2 - version: 7.0.0-beta.2(@storybook/addon-docs@7.2.0)(@storybook/addons@6.5.16)(@storybook/api@6.5.16)(@storybook/components@7.2.0)(@storybook/theming@7.2.0)(react-dom@18.2.0)(react@18.2.0) storybook-addon-pseudo-states: specifier: 2.1.0 version: 2.1.0(@storybook/components@7.2.0)(@storybook/core-events@7.2.0)(@storybook/manager-api@7.2.0)(@storybook/preview-api@7.2.0)(@storybook/theming@7.2.0)(react-dom@18.2.0)(react@18.2.0) @@ -9108,6 +9105,34 @@ packages: - supports-color dev: true + /@storybook/addon-designs@7.0.1(@storybook/addon-docs@7.2.0)(@storybook/addons@7.2.0)(@storybook/components@7.2.0)(@storybook/manager-api@7.2.0)(@storybook/preview-api@7.2.0)(@storybook/theming@7.2.0)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-bp7eoOYnoKp48H7tx0nkhhi9y7qUBr2YE9pd2DdxlS+4s2REglVeh1bNbBEYg9QUKPeKgUoWLMonLMTuCUyLoQ==} + peerDependencies: + '@storybook/addon-docs': ^7.0.0 + '@storybook/addons': ^7.0.0 + '@storybook/components': ^7.0.0 || 7 + '@storybook/manager-api': ^7.0.0 + '@storybook/preview-api': ^7.0.0 + '@storybook/theming': ^7.0.0 || 7 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + dependencies: + '@figspec/react': 1.0.3(react@18.2.0) + '@storybook/addon-docs': 7.2.0(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + '@storybook/addons': 7.2.0(react-dom@18.2.0)(react@18.2.0) + '@storybook/components': 7.2.0(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + '@storybook/manager-api': 7.2.0(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.2.0 + '@storybook/theming': 7.2.0(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /@storybook/addon-docs@7.2.0(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-QXScPt5HhKvNllFCBlq5Gr7reNnaBGJZmOzw1QypzKSkIdx18WtKKOppxa5AwETXFnJ8XSjwcuLzZ+q5RUNiww==} peerDependencies: @@ -9339,27 +9364,6 @@ packages: - '@types/react-dom' dev: true - /@storybook/addons@6.5.16(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-p3DqQi+8QRL5k7jXhXmJZLsE/GqHqyY6PcoA1oNTJr0try48uhTGUOYkgzmqtDaa/qPFO5LP+xCPzZXckGtquQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - dependencies: - '@storybook/api': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/channels': 6.5.16 - '@storybook/client-logger': 6.5.16 - '@storybook/core-events': 6.5.16 - '@storybook/csf': 0.0.2--canary.4566f4d.1 - '@storybook/router': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/theming': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@types/webpack-env': 1.18.0 - core-js: 3.30.0 - global: 4.4.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - regenerator-runtime: 0.13.11 - dev: true - /@storybook/addons@7.2.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-T9sH/CJASBUR8RnirwyMU0zBqT4fvdq0sSmgZlbonuU5eKhOfguwZqd3Sx6xMoWeJuHnyk+wcWWTlkmJremV+w==} peerDependencies: @@ -9373,33 +9377,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/api@6.5.16(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-HOsuT8iomqeTMQJrRx5U8nsC7lJTwRr1DhdD0SzlqL4c80S/7uuCy4IZvOt4sYQjOzW5fOo/kamcoBXyLproTA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - dependencies: - '@storybook/channels': 6.5.16 - '@storybook/client-logger': 6.5.16 - '@storybook/core-events': 6.5.16 - '@storybook/csf': 0.0.2--canary.4566f4d.1 - '@storybook/router': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/semver': 7.3.2 - '@storybook/theming': 6.5.16(react-dom@18.2.0)(react@18.2.0) - core-js: 3.30.0 - fast-deep-equal: 3.1.3 - global: 4.4.0 - lodash: 4.17.21 - memoizerific: 1.11.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - regenerator-runtime: 0.13.11 - store2: 2.14.2 - telejson: 6.0.8 - ts-dedent: 2.2.0 - util-deprecate: 1.0.2 - dev: true - /@storybook/blocks@7.2.0(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-QbRw4lszmkxD86+murh0rJWa2rf9fEhI3vP3BSD+Ta6YgLHt+T94l0K5uQpESs8DRWHFGe5kT33hcAXHIpBqPA==} peerDependencies: @@ -9530,14 +9507,6 @@ packages: - webpack-cli dev: true - /@storybook/channels@6.5.16: - resolution: {integrity: sha512-VylzaWQZaMozEwZPJdyJoz+0jpDa8GRyaqu9TGG6QGv+KU5POoZaGLDkRE7TzWkyyP0KQLo80K99MssZCpgSeg==} - dependencies: - core-js: 3.30.0 - ts-dedent: 2.2.0 - util-deprecate: 1.0.2 - dev: true - /@storybook/channels@7.2.0: resolution: {integrity: sha512-2W0tnfmyPQc3TES1NgFOzrmnzVkxqEYGqWYOYQkwgumC+FIDIktW02eIP8JlxuStEx4oIMuB8YF3euRNZqHSgA==} dependencies: @@ -9607,13 +9576,6 @@ packages: '@storybook/preview-api': 7.2.0 dev: true - /@storybook/client-logger@6.5.16: - resolution: {integrity: sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==} - dependencies: - core-js: 3.30.0 - global: 4.4.0 - dev: true - /@storybook/client-logger@7.2.0: resolution: {integrity: sha512-e31snLKvP2h/BBl+DXR/pM/CI8uvDU89BujHniK3ttJNynjOpJmHp0SgxOKnlRXpOaau9jKKoLVMegi/BgIYpA==} dependencies: @@ -9700,12 +9662,6 @@ packages: - supports-color dev: true - /@storybook/core-events@6.5.16: - resolution: {integrity: sha512-qMZQwmvzpH5F2uwNUllTPg6eZXr2OaYZQRRN8VZJiuorZzDNdAFmiVWMWdkThwmyLEJuQKXxqCL8lMj/7PPM+g==} - dependencies: - core-js: 3.30.0 - dev: true - /@storybook/core-events@7.2.0: resolution: {integrity: sha512-Y1o8vGBnbZ/bYsukPiK33CHURSob3tywg8WRtAuwWnDaZiM9IXgkEHbOK1zfkPTnz2gSXEX19KlpTmMxm0W//w==} dev: true @@ -9807,12 +9763,6 @@ packages: lodash: 4.17.21 dev: true - /@storybook/csf@0.0.2--canary.4566f4d.1: - resolution: {integrity: sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ==} - dependencies: - lodash: 4.17.21 - dev: true - /@storybook/csf@0.1.1: resolution: {integrity: sha512-4hE3AlNVxR60Wc5KSC68ASYzUobjPqtSKyhV6G+ge0FIXU55N5nTY7dXGRZHQGDBPq+XqchMkIdlkHPRs8nTHg==} dependencies: @@ -10134,21 +10084,6 @@ packages: - supports-color dev: true - /@storybook/router@6.5.16(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-ZgeP8a5YV/iuKbv31V8DjPxlV4AzorRiR8OuSt/KqaiYXNXlOoQDz/qMmiNcrshrfLpmkzoq7fSo4T8lWo2UwQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - dependencies: - '@storybook/client-logger': 6.5.16 - core-js: 3.30.0 - memoizerific: 1.11.3 - qs: 6.11.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - regenerator-runtime: 0.13.11 - dev: true - /@storybook/router@7.2.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-8QQ3qzNKy75QSVY4JhtYJI/EBLAepTkMpOcbdvgufFWOwTm/s9N5VlGpVctHYNf+vvNpX+YndVoMGAU7bdn8EQ==} peerDependencies: @@ -10162,15 +10097,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/semver@7.3.2: - resolution: {integrity: sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==} - engines: {node: '>=10'} - hasBin: true - dependencies: - core-js: 3.30.0 - find-up: 4.1.0 - dev: true - /@storybook/store@7.2.0: resolution: {integrity: sha512-UHbx2HKcsll8xSt0SvXQQNOL6ijS64xVLnxDKM87jWyB7CXEjEFLFylezo9+7iUazVtXAyr26Nj/VvH0e5qCOw==} dependencies: @@ -10243,20 +10169,6 @@ packages: ts-dedent: 2.2.0 dev: true - /@storybook/theming@6.5.16(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-hNLctkjaYLRdk1+xYTkC1mg4dYz2wSv6SqbLpcKMbkPHTE0ElhddGPHQqB362md/w9emYXNkt1LSMD8Xk9JzVQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - dependencies: - '@storybook/client-logger': 6.5.16 - core-js: 3.30.0 - memoizerific: 1.11.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - regenerator-runtime: 0.13.11 - dev: true - /@storybook/theming@7.2.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-E/nFqZuHnR1HK/jXwlPzLnmbIDxWg4cbSkX3sfTbsDd1h7YhxbouheYSuSPqVDjk+3F87Tv2CP+cZUKDkPd3pQ==} peerDependencies: @@ -11242,10 +11154,6 @@ packages: /@types/is-empty@1.2.1: resolution: {integrity: sha512-a3xgqnFTuNJDm1fjsTjHocYJ40Cz3t8utYpi5GNaxzrJC2HSD08ym+whIL7fNqiqBCdM9bcqD1H/tORWAFXoZw==} - /@types/is-function@1.0.1: - resolution: {integrity: sha512-A79HEEiwXTFtfY+Bcbo58M2GRYzCr9itHWzbzHVFNEYCcoU/MMGwYYf721gBrnhpj1s6RGVVha/IgNFnR0Iw/Q==} - dev: true - /@types/iso-3166-2@1.0.0: resolution: {integrity: sha512-DYDyoRyPyxBeI9bYoTXLfsOZH12m1anrWEj9LU5Sl9rgsJ4soJMYf/7byozM+64Smn6/a1i079eQLGuPykwaHQ==} dev: true @@ -11555,10 +11463,6 @@ packages: resolution: {integrity: sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==} dev: true - /@types/webpack-env@1.18.0: - resolution: {integrity: sha512-56/MAlX5WMsPVbOg7tAxnYvNYMMWr/QJiIp6BxVSW3JJXUVzzOn64qW8TzQyMSqSUFM2+PVI4aUHcHOzIz/1tg==} - dev: true - /@types/whatwg-url@8.2.2: resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==} dependencies: @@ -13916,31 +13820,6 @@ packages: dependencies: type-fest: 1.4.0 - /css-chaos-addon@0.0.5(@storybook/addons@6.5.16)(@storybook/api@6.5.16)(@storybook/components@7.2.0)(@storybook/core-events@7.2.0)(@storybook/theming@7.2.0)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-h9AravpeFHIPAHcYVNyHTph7Jv3GGviiurddlIv2aY3Zu0SwQa567MWBxgZVLc5KhEX4qUCADbuJLycEKsjcGg==} - peerDependencies: - '@storybook/addons': ^6.5.8 - '@storybook/api': ^6.5.8 - '@storybook/components': ^6.5.8 || 7 - '@storybook/core-events': ^6.5.8 || 7 - '@storybook/theming': ^6.5.8 || 7 - react: ^16.8.0 || ^17.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - dependencies: - '@storybook/addons': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/api': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/components': 7.2.0(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.2.0 - '@storybook/theming': 7.2.0(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: true - /css-loader@6.8.1(webpack@5.88.2): resolution: {integrity: sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==} engines: {node: '>= 12.13.0'} @@ -14357,10 +14236,6 @@ packages: domhandler: 4.3.1 entities: 2.2.0 - /dom-walk@0.1.2: - resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} - dev: true - /domain-browser@4.22.0: resolution: {integrity: sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==} engines: {node: '>=10'} @@ -16287,13 +16162,6 @@ packages: which: 1.3.1 dev: true - /global@4.4.0: - resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==} - dependencies: - min-document: 2.19.0 - process: 0.11.10 - dev: true - /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -17253,10 +17121,6 @@ packages: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} engines: {node: '>=12'} - /is-function@1.0.2: - resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==} - dev: true - /is-generator-fn@2.1.0: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} @@ -17510,11 +17374,6 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} - /isobject@4.0.0: - resolution: {integrity: sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==} - engines: {node: '>=0.10.0'} - dev: true - /isomorphic-fetch@3.0.0: resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} dependencies: @@ -19794,12 +19653,6 @@ packages: resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - /min-document@2.19.0: - resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==} - dependencies: - dom-walk: 0.1.2 - dev: true - /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -23623,33 +23476,6 @@ packages: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} dev: true - /storybook-addon-designs@7.0.0-beta.2(@storybook/addon-docs@7.2.0)(@storybook/addons@6.5.16)(@storybook/api@6.5.16)(@storybook/components@7.2.0)(@storybook/theming@7.2.0)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-ljBNmyCJdPTXhiBSfA1S+GBxtMooW2M7nxlt49OoCRH7jcxZOYQdiI8JYQiMF5Ur0MGakbSci0Xm+JzAvcm02g==} - deprecated: Newer versions of this package is available under `@storybook/addon-designs` - peerDependencies: - '@storybook/addon-docs': ^6.4.0 || ^7.0.0 - '@storybook/addons': ^6.4.0 || ^7.0.0 - '@storybook/api': ^6.4.0 || ^7.0.0 - '@storybook/components': ^6.4.0 || ^7.0.0 || 7 - '@storybook/theming': ^6.4.0 || ^7.0.0 || 7 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - dependencies: - '@figspec/react': 1.0.3(react@18.2.0) - '@storybook/addon-docs': 7.2.0(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) - '@storybook/addons': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/api': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/components': 7.2.0(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) - '@storybook/theming': 7.2.0(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: true - /storybook-addon-pseudo-states@2.1.0(@storybook/components@7.2.0)(@storybook/core-events@7.2.0)(@storybook/manager-api@7.2.0)(@storybook/preview-api@7.2.0)(@storybook/theming@7.2.0)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-AwbCL1OiZ16aIeXSP/IOovkMwXy7NTZqmjkz+UM2guSGjvogHNA95NhuVyWoqieE+QWUpGO48+MrBGMeeJcHOQ==} peerDependencies: @@ -24104,19 +23930,6 @@ packages: mkdirp: 1.0.4 yallist: 4.0.0 - /telejson@6.0.8: - resolution: {integrity: sha512-nerNXi+j8NK1QEfBHtZUN/aLdDcyupA//9kAboYLrtzZlPLpUfqbVGWb9zz91f/mIjRbAYhbgtnJHY8I1b5MBg==} - dependencies: - '@types/is-function': 1.0.1 - global: 4.4.0 - is-function: 1.0.2 - is-regex: 1.1.4 - is-symbol: 1.0.4 - isobject: 4.0.0 - lodash: 4.17.21 - memoizerific: 1.11.3 - dev: true - /telejson@7.1.0: resolution: {integrity: sha512-jFJO4P5gPebZAERPkJsqMAQ0IMA1Hi0AoSfxpnUaV6j6R2SZqlpkbS20U6dEUtA3RUYt2Ak/mTlkQzHH9Rv/hA==} dependencies: From 6d01d35db4c8673cb82a6f460fc45c8f28763207 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Fri, 4 Aug 2023 20:28:04 -0400 Subject: [PATCH 07/63] lazy load orgLocation query handlers --- packages/api/router/location/queries.ts | 286 +++++------------ .../location/query.forGoogleMaps.handler.ts | 42 +++ .../location/query.forGoogleMaps.schema.ts | 6 + .../location/query.forLocationCard.handler.ts | 57 ++++ .../location/query.forLocationCard.schema.ts | 6 + .../location/query.forLocationPage.handler.ts | 40 +++ .../location/query.forLocationPage.schema.ts | 6 + .../location/query.forVisitCard.handler.ts | 39 +++ .../location/query.forVisitCard.schema.ts | 6 + .../location/query.getAddress.handler.ts | 57 ++++ .../location/query.getAddress.schema.ts | 6 + .../router/location/query.getById.handler.ts | 59 ++++ .../router/location/query.getById.schema.ts | 6 + .../location/query.getByOrgId.handler.ts | 62 ++++ .../location/query.getByOrgId.schema.ts | 6 + .../location/query.getNameById.handler.ts | 17 + .../location/query.getNameById.schema.ts | 6 + .../router/location/query.getNames.handler.ts | 23 ++ .../router/location/query.getNames.schema.ts | 6 + packages/api/router/location/schemas.ts | 14 + packages/api/router/location/selects.ts | 291 ++++++++++++++++++ 21 files changed, 824 insertions(+), 217 deletions(-) create mode 100644 packages/api/router/location/query.forGoogleMaps.handler.ts create mode 100644 packages/api/router/location/query.forGoogleMaps.schema.ts create mode 100644 packages/api/router/location/query.forLocationCard.handler.ts create mode 100644 packages/api/router/location/query.forLocationCard.schema.ts create mode 100644 packages/api/router/location/query.forLocationPage.handler.ts create mode 100644 packages/api/router/location/query.forLocationPage.schema.ts create mode 100644 packages/api/router/location/query.forVisitCard.handler.ts create mode 100644 packages/api/router/location/query.forVisitCard.schema.ts create mode 100644 packages/api/router/location/query.getAddress.handler.ts create mode 100644 packages/api/router/location/query.getAddress.schema.ts create mode 100644 packages/api/router/location/query.getById.handler.ts create mode 100644 packages/api/router/location/query.getById.schema.ts create mode 100644 packages/api/router/location/query.getByOrgId.handler.ts create mode 100644 packages/api/router/location/query.getByOrgId.schema.ts create mode 100644 packages/api/router/location/query.getNameById.handler.ts create mode 100644 packages/api/router/location/query.getNameById.schema.ts create mode 100644 packages/api/router/location/query.getNames.handler.ts create mode 100644 packages/api/router/location/query.getNames.schema.ts create mode 100644 packages/api/router/location/schemas.ts create mode 100644 packages/api/router/location/selects.ts diff --git a/packages/api/router/location/queries.ts b/packages/api/router/location/queries.ts index aa6470d075..c003de8ced 100644 --- a/packages/api/router/location/queries.ts +++ b/packages/api/router/location/queries.ts @@ -1,245 +1,97 @@ -import { TRPCError } from '@trpc/server' import { z } from 'zod' import { handleError } from '~api/lib/errorHandler' import { defineRouter, permissionedProcedure, publicProcedure } from '~api/lib/trpc' import { id, orgId } from '~api/schemas/common' import { attributes, freeText, isPublic } from '~api/schemas/selects/common' -import { orgLocationInclude } from '~api/schemas/selects/org' + +import * as schema from './schemas' + +export const HandlerCache: Partial = {} +type LocationHandlerCache = { + getById: typeof import('./query.getById.handler').getById + getByOrgId: typeof import('./query.getByOrgId.handler').getByOrgId + getNameById: typeof import('./query.getNameById.handler').getNameById + getNames: typeof import('./query.getNames.handler').getNames + getAddress: typeof import('./query.getAddress.handler').getAddress + forLocationCard: typeof import('./query.forLocationCard.handler').forLocationCard + forVisitCard: typeof import('./query.forVisitCard.handler').forVisitCard + forGoogleMaps: typeof import('./query.forGoogleMaps.handler').forGoogleMaps + forLocationPage: typeof import('./query.forLocationPage.handler').forLocationPage +} export const queries = defineRouter({ - getById: publicProcedure.input(id).query(async ({ ctx, input }) => { - try { - const location = await ctx.prisma.orgLocation.findUniqueOrThrow({ - where: { - id: input.id, - ...isPublic, - }, - select: orgLocationInclude(ctx).select, - }) - return location - } catch (error) { - handleError(error) + getById: publicProcedure.input(schema.ZGetByIdSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getById) { + HandlerCache.getById = await import('./query.getById.handler').then((mod) => mod.getById) + } + if (!HandlerCache.getById) throw new Error('Failed to load handler') + return HandlerCache.getById({ ctx, input }) + }), + getByOrgId: publicProcedure.input(schema.ZGetByOrgIdSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getByOrgId) { + HandlerCache.getByOrgId = await import('./query.getByOrgId.handler').then((mod) => mod.getByOrgId) } + if (!HandlerCache.getByOrgId) throw new Error('Failed to load handler') + return HandlerCache.getByOrgId({ ctx, input }) }), - getByOrgId: publicProcedure.input(orgId).query(async ({ ctx, input }) => { - try { - const locations = await ctx.prisma.orgLocation.findMany({ - where: { - orgId: input.orgId, - ...isPublic, - }, - select: orgLocationInclude(ctx).select, - }) - if (locations.length === 0) throw new TRPCError({ code: 'NOT_FOUND' }) - return locations - } catch (error) { - handleError(error) + getNameById: publicProcedure.input(schema.ZGetNameByIdSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getNameById) { + HandlerCache.getNameById = await import('./query.getNameById.handler').then((mod) => mod.getNameById) } + if (!HandlerCache.getNameById) throw new Error('Failed to load handler') + return HandlerCache.getNameById({ ctx, input }) }), - getNameById: publicProcedure.input(z.string()).query(async ({ ctx, input }) => - ctx.prisma.orgLocation.findUniqueOrThrow({ - where: { id: input }, - select: { name: true }, - }) - ), getNames: permissionedProcedure('getDetails') - .input(z.object({ organizationId: z.string() })) + .input(schema.ZGetNamesSchema) .query(async ({ ctx, input }) => { - const { organizationId } = input - - if (!organizationId) throw new TRPCError({ code: 'BAD_REQUEST' }) - - const results = await ctx.prisma.orgLocation.findMany({ - where: { - organization: { id: organizationId }, - }, - select: { - id: true, - name: true, - }, - }) - - return results + if (!HandlerCache.getNames) { + HandlerCache.getNames = await import('./query.getNames.handler').then((mod) => mod.getNames) + } + if (!HandlerCache.getNames) throw new Error('Failed to load handler') + return HandlerCache.getNames({ ctx, input }) }), getAddress: permissionedProcedure('updateLocation') - .input(z.string()) + .input(schema.ZGetAddressSchema) .query(async ({ ctx, input }) => { - const result = await ctx.prisma.orgLocation.findUniqueOrThrow({ - where: { id: input }, - select: { - id: true, - name: true, - street1: true, - street2: true, - city: true, - postCode: true, - govDistId: true, - countryId: true, - attributes: { - select: { - attribute: { select: { id: true, tsKey: true, tsNs: true } }, - supplement: { select: { id: true, boolean: true } }, - }, - where: { attribute: { tag: 'wheelchair-accessible' } }, - }, - latitude: true, - longitude: true, - mailOnly: true, - published: true, - services: { select: { serviceId: true } }, - }, - }) - const { id, attributes, services, ...rest } = result - - const accessibleAttribute = attributes.find(({ supplement }) => Boolean(supplement.length)) - const { id: supplementId, boolean } = accessibleAttribute - ? accessibleAttribute.supplement.find(({ id }) => Boolean(id)) ?? {} - : { id: undefined, boolean: undefined } - - const transformedResult = { - id, - data: { - ...rest, - accessible: { - supplementId, - boolean, - }, - services: services.map(({ serviceId }) => serviceId), - }, + if (!HandlerCache.getAddress) { + HandlerCache.getAddress = await import('./query.getAddress.handler').then((mod) => mod.getAddress) } - - return transformedResult + if (!HandlerCache.getAddress) throw new Error('Failed to load handler') + return HandlerCache.getAddress({ ctx, input }) }), - forLocationCard: publicProcedure.input(z.string()).query(async ({ ctx, input }) => { - const result = await ctx.prisma.orgLocation.findUniqueOrThrow({ - where: { - id: input, - ...isPublic, - }, - select: { - id: true, - name: true, - street1: true, - street2: true, - city: true, - postCode: true, - country: { select: { cca2: true } }, - govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, - phones: { - where: { phone: isPublic }, - select: { phone: { select: { primary: true, number: true, country: { select: { cca2: true } } } } }, - }, - attributes: { select: { attribute: { select: { tsNs: true, tsKey: true, icon: true } } } }, - services: { - select: { - service: { - select: { - services: { select: { tag: { select: { category: { select: { tsKey: true } } } } } }, - }, - }, - }, - }, - }, - }) - - const transformed = { - ...result, - country: result.country.cca2, - phones: result.phones.map(({ phone }) => ({ ...phone, country: phone.country.cca2 })), - attributes: result.attributes.map(({ attribute }) => attribute), - services: [ - ...new Set( - result.services.flatMap(({ service }) => service.services.map(({ tag }) => tag.category.tsKey)) - ), - ], + forLocationCard: publicProcedure.input(schema.ZForLocationCardSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forLocationCard) { + HandlerCache.forLocationCard = await import('./query.forLocationCard.handler').then( + (mod) => mod.forLocationCard + ) } - - return transformed + if (!HandlerCache.forLocationCard) throw new Error('Failed to load handler') + return HandlerCache.forLocationCard({ ctx, input }) }), - forVisitCard: publicProcedure.input(z.string()).query(async ({ ctx, input }) => { - const result = await ctx.prisma.orgLocation.findUniqueOrThrow({ - where: { - ...isPublic, - id: input, - }, - select: { - id: true, - street1: true, - street2: true, - city: true, - postCode: true, - country: { select: { cca2: true } }, - govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, - attributes: { - where: { attribute: { tsKey: 'additional.offers-remote-services' } }, - select: { attribute: { select: { tsKey: true, icon: true } } }, - }, - }, - }) - const { attributes, ...rest } = result - const transformed = { - ...rest, - remote: attributes.find(({ attribute }) => attribute.tsKey === 'additional.offers-remote-services') - ?.attribute, + forVisitCard: publicProcedure.input(schema.ZForVisitCardSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forVisitCard) { + HandlerCache.forVisitCard = await import('./query.forVisitCard.handler').then((mod) => mod.forVisitCard) } - return transformed + if (!HandlerCache.forVisitCard) throw new Error('Failed to load handler') + return HandlerCache.forVisitCard({ ctx, input }) }), - forGoogleMaps: publicProcedure.input(z.string().or(z.string().array())).query(async ({ ctx, input }) => { - const select = { - id: true, - name: true, - latitude: true, - longitude: true, + forGoogleMaps: publicProcedure.input(schema.ZForGoogleMapsSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forGoogleMaps) { + HandlerCache.forGoogleMaps = await import('./query.forGoogleMaps.handler').then( + (mod) => mod.forGoogleMaps + ) } - - const result = Array.isArray(input) - ? await ctx.prisma.orgLocation.findMany({ - where: { - ...isPublic, - id: { in: input }, - }, - select, - }) - : await ctx.prisma.orgLocation.findUniqueOrThrow({ - where: { - ...isPublic, - id: input, - }, - select, - }) - return result + if (!HandlerCache.forGoogleMaps) throw new Error('Failed to load handler') + return HandlerCache.forGoogleMaps({ ctx, input }) }), - forLocationPage: publicProcedure.input(id).query(async ({ ctx, input }) => { - try { - const location = await ctx.prisma.orgLocation.findUniqueOrThrow({ - where: { - id: input.id, - ...isPublic, - }, - select: { - id: true, - primary: true, - name: true, - street1: true, - street2: true, - city: true, - postCode: true, - country: { select: { cca2: true } }, - govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, - longitude: true, - latitude: true, - description: freeText, - attributes, - reviews: { - where: { visible: true, deleted: false }, - select: { id: true }, - }, - }, - }) - return location - } catch (error) { - handleError(error) + forLocationPage: publicProcedure.input(schema.ZForLocationPageSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forLocationPage) { + HandlerCache.forLocationPage = await import('./query.forLocationPage.handler').then( + (mod) => mod.forLocationPage + ) } + if (!HandlerCache.forLocationPage) throw new Error('Failed to load handler') + return HandlerCache.forLocationPage({ ctx, input }) }), }) diff --git a/packages/api/router/location/query.forGoogleMaps.handler.ts b/packages/api/router/location/query.forGoogleMaps.handler.ts new file mode 100644 index 0000000000..d0957a8c10 --- /dev/null +++ b/packages/api/router/location/query.forGoogleMaps.handler.ts @@ -0,0 +1,42 @@ +import { TRPCError } from '@trpc/server' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForGoogleMapsSchema } from './query.forGoogleMaps.schema' + +export const forGoogleMaps = async ({ input }: TRPCHandlerParams) => { + try { + const select = { + id: true, + name: true, + latitude: true, + longitude: true, + } + + const result = Array.isArray(input) + ? await prisma.orgLocation.findMany({ + where: { + ...globalWhere.isPublic(), + id: { in: input }, + }, + select, + }) + : await prisma.orgLocation.findUniqueOrThrow({ + where: { + ...globalWhere.isPublic(), + id: input, + }, + select, + }) + + if (Array.isArray(input) && Array.isArray(result) && !result.length) { + throw new TRPCError({ code: 'NOT_FOUND' }) + } + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/location/query.forGoogleMaps.schema.ts b/packages/api/router/location/query.forGoogleMaps.schema.ts new file mode 100644 index 0000000000..03caab8287 --- /dev/null +++ b/packages/api/router/location/query.forGoogleMaps.schema.ts @@ -0,0 +1,6 @@ +import { type z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForGoogleMapsSchema = prefixedId('orgLocation').or(prefixedId('orgLocation').array()) +export type TForGoogleMapsSchema = z.infer diff --git a/packages/api/router/location/query.forLocationCard.handler.ts b/packages/api/router/location/query.forLocationCard.handler.ts new file mode 100644 index 0000000000..4112138ad1 --- /dev/null +++ b/packages/api/router/location/query.forLocationCard.handler.ts @@ -0,0 +1,57 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForLocationCardSchema } from './query.forLocationCard.schema' + +export const forLocationCard = async ({ input }: TRPCHandlerParams) => { + try { + const result = await prisma.orgLocation.findUniqueOrThrow({ + where: { + id: input, + ...globalWhere.isPublic(), + }, + select: { + id: true, + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + country: { select: { cca2: true } }, + govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, + phones: { + where: { phone: globalWhere.isPublic() }, + select: { phone: { select: { primary: true, number: true, country: { select: { cca2: true } } } } }, + }, + attributes: { select: { attribute: { select: { tsNs: true, tsKey: true, icon: true } } } }, + services: { + select: { + service: { + select: { + services: { select: { tag: { select: { category: { select: { tsKey: true } } } } } }, + }, + }, + }, + }, + }, + }) + + const transformed = { + ...result, + country: result.country.cca2, + phones: result.phones.map(({ phone }) => ({ ...phone, country: phone.country.cca2 })), + attributes: result.attributes.map(({ attribute }) => attribute), + services: [ + ...new Set( + result.services.flatMap(({ service }) => service.services.map(({ tag }) => tag.category.tsKey)) + ), + ], + } + + return transformed + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/location/query.forLocationCard.schema.ts b/packages/api/router/location/query.forLocationCard.schema.ts new file mode 100644 index 0000000000..2653390114 --- /dev/null +++ b/packages/api/router/location/query.forLocationCard.schema.ts @@ -0,0 +1,6 @@ +import { type z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForLocationCardSchema = prefixedId('orgLocation') +export type TForLocationCardSchema = z.infer diff --git a/packages/api/router/location/query.forLocationPage.handler.ts b/packages/api/router/location/query.forLocationPage.handler.ts new file mode 100644 index 0000000000..b6e7890f40 --- /dev/null +++ b/packages/api/router/location/query.forLocationPage.handler.ts @@ -0,0 +1,40 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalSelect, globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForLocationPageSchema } from './query.forLocationPage.schema' +import { select } from './selects' + +export const forLocationPage = async ({ input }: TRPCHandlerParams) => { + try { + const location = await prisma.orgLocation.findUniqueOrThrow({ + where: { + id: input.id, + ...globalWhere.isPublic(), + }, + select: { + id: true, + primary: true, + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + country: { select: { cca2: true } }, + govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, + longitude: true, + latitude: true, + description: globalSelect.freeText(), + attributes: select.attributes(), + reviews: { + where: { visible: true, deleted: false }, + select: { id: true }, + }, + }, + }) + return location + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/location/query.forLocationPage.schema.ts b/packages/api/router/location/query.forLocationPage.schema.ts new file mode 100644 index 0000000000..ceae53f37d --- /dev/null +++ b/packages/api/router/location/query.forLocationPage.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForLocationPageSchema = z.object({ id: prefixedId('orgLocation') }) +export type TForLocationPageSchema = z.infer diff --git a/packages/api/router/location/query.forVisitCard.handler.ts b/packages/api/router/location/query.forVisitCard.handler.ts new file mode 100644 index 0000000000..c3ee7db106 --- /dev/null +++ b/packages/api/router/location/query.forVisitCard.handler.ts @@ -0,0 +1,39 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForVisitCardSchema } from './query.forVisitCard.schema' + +export const forVisitCard = async ({ input }: TRPCHandlerParams) => { + try { + const result = await prisma.orgLocation.findUniqueOrThrow({ + where: { + ...globalWhere.isPublic(), + id: input, + }, + select: { + id: true, + street1: true, + street2: true, + city: true, + postCode: true, + country: { select: { cca2: true } }, + govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, + attributes: { + where: { attribute: { tsKey: 'additional.offers-remote-services' } }, + select: { attribute: { select: { tsKey: true, icon: true } } }, + }, + }, + }) + const { attributes, ...rest } = result + const transformed = { + ...rest, + remote: attributes.find(({ attribute }) => attribute.tsKey === 'additional.offers-remote-services') + ?.attribute, + } + return transformed + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/location/query.forVisitCard.schema.ts b/packages/api/router/location/query.forVisitCard.schema.ts new file mode 100644 index 0000000000..444d3936af --- /dev/null +++ b/packages/api/router/location/query.forVisitCard.schema.ts @@ -0,0 +1,6 @@ +import { type z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForVisitCardSchema = prefixedId('orgLocation') +export type TForVisitCardSchema = z.infer diff --git a/packages/api/router/location/query.getAddress.handler.ts b/packages/api/router/location/query.getAddress.handler.ts new file mode 100644 index 0000000000..2854ea13a7 --- /dev/null +++ b/packages/api/router/location/query.getAddress.handler.ts @@ -0,0 +1,57 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetAddressSchema } from './query.getAddress.schema' + +export const getAddress = async ({ input }: TRPCHandlerParams) => { + try { + const result = await prisma.orgLocation.findUniqueOrThrow({ + where: { id: input }, + select: { + id: true, + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + govDistId: true, + countryId: true, + attributes: { + select: { + attribute: { select: { id: true, tsKey: true, tsNs: true } }, + supplement: { select: { id: true, boolean: true } }, + }, + where: { attribute: { tag: 'wheelchair-accessible' } }, + }, + latitude: true, + longitude: true, + mailOnly: true, + published: true, + services: { select: { serviceId: true } }, + }, + }) + const { id, attributes, services, ...rest } = result + + const accessibleAttribute = attributes.find(({ supplement }) => Boolean(supplement.length)) + const { id: supplementId, boolean } = accessibleAttribute + ? accessibleAttribute.supplement.find(({ id }) => Boolean(id)) ?? {} + : { id: undefined, boolean: undefined } + + const transformedResult = { + id, + data: { + ...rest, + accessible: { + supplementId, + boolean, + }, + services: services.map(({ serviceId }) => serviceId), + }, + } + + return transformedResult + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/location/query.getAddress.schema.ts b/packages/api/router/location/query.getAddress.schema.ts new file mode 100644 index 0000000000..124104df8d --- /dev/null +++ b/packages/api/router/location/query.getAddress.schema.ts @@ -0,0 +1,6 @@ +import { type z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetAddressSchema = prefixedId('orgLocation') +export type TGetAddressSchema = z.infer diff --git a/packages/api/router/location/query.getById.handler.ts b/packages/api/router/location/query.getById.handler.ts new file mode 100644 index 0000000000..533cb59e2a --- /dev/null +++ b/packages/api/router/location/query.getById.handler.ts @@ -0,0 +1,59 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalSelect, globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetByIdSchema } from './query.getById.schema' +import { select } from './selects' + +export const getById = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const location = await prisma.orgLocation.findUniqueOrThrow({ + where: { + id: input.id, + ...globalWhere.isPublic(), + }, + select: { + govDist: select.govDistBasic(), + country: select.country(), + attributes: { + where: { + attribute: { + active: true, + categories: { + some: { + category: { + active: true, + }, + }, + }, + }, + }, + ...select.attributes(), + }, + emails: { where: { email: globalWhere.isPublic() }, ...select.orgEmail() }, + websites: { where: globalWhere.isPublic(), ...select.orgWebsite() }, + phones: { where: { phone: globalWhere.isPublic() }, ...select.orgPhone() }, + photos: { where: globalWhere.isPublic(), ...select.orgPhoto() }, + hours: select.hours(), + reviews: { where: { visible: true, deleted: false }, select: { id: true } }, + services: { where: { service: globalWhere.isPublic() }, ...select.service(ctx) }, + serviceAreas: select.serviceArea(), + socialMedia: { where: globalWhere.isPublic(), ...select.socialMedia() }, + description: globalSelect.freeText(), + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + primary: true, + longitude: true, + latitude: true, + id: true, + }, + }) + return location + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/location/query.getById.schema.ts b/packages/api/router/location/query.getById.schema.ts new file mode 100644 index 0000000000..1d76f708c9 --- /dev/null +++ b/packages/api/router/location/query.getById.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetByIdSchema = z.object({ id: prefixedId('orgLocation') }) +export type TGetByIdSchema = z.infer diff --git a/packages/api/router/location/query.getByOrgId.handler.ts b/packages/api/router/location/query.getByOrgId.handler.ts new file mode 100644 index 0000000000..91e3f66f36 --- /dev/null +++ b/packages/api/router/location/query.getByOrgId.handler.ts @@ -0,0 +1,62 @@ +import { TRPCError } from '@trpc/server' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalSelect, globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetByOrgIdSchema } from './query.getByOrgId.schema' +import { select } from './selects' + +export const getByOrgId = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const locations = await prisma.orgLocation.findMany({ + where: { + id: input.orgId, + ...globalWhere.isPublic(), + }, + select: { + govDist: select.govDistBasic(), + country: select.country(), + attributes: { + where: { + attribute: { + active: true, + categories: { + some: { + category: { + active: true, + }, + }, + }, + }, + }, + ...select.attributes(), + }, + emails: { where: { email: globalWhere.isPublic() }, ...select.orgEmail() }, + websites: { where: globalWhere.isPublic(), ...select.orgWebsite() }, + phones: { where: { phone: globalWhere.isPublic() }, ...select.orgPhone() }, + photos: { where: globalWhere.isPublic(), ...select.orgPhoto() }, + hours: select.hours(), + reviews: { where: { visible: true, deleted: false }, select: { id: true } }, + services: { where: { service: globalWhere.isPublic() }, ...select.service(ctx) }, + serviceAreas: select.serviceArea(), + socialMedia: { where: globalWhere.isPublic(), ...select.socialMedia() }, + description: globalSelect.freeText(), + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + primary: true, + longitude: true, + latitude: true, + id: true, + }, + }) + if (locations.length === 0) throw new TRPCError({ code: 'NOT_FOUND' }) + return locations + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/location/query.getByOrgId.schema.ts b/packages/api/router/location/query.getByOrgId.schema.ts new file mode 100644 index 0000000000..a0223b8b23 --- /dev/null +++ b/packages/api/router/location/query.getByOrgId.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetByOrgIdSchema = z.object({ orgId: prefixedId('organization') }) +export type TGetByOrgIdSchema = z.infer diff --git a/packages/api/router/location/query.getNameById.handler.ts b/packages/api/router/location/query.getNameById.handler.ts new file mode 100644 index 0000000000..f702f5b874 --- /dev/null +++ b/packages/api/router/location/query.getNameById.handler.ts @@ -0,0 +1,17 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetNameByIdSchema } from './query.getNameById.schema' + +export const getNameById = async ({ input }: TRPCHandlerParams) => { + try { + const result = await prisma.orgLocation.findUniqueOrThrow({ + where: { id: input }, + select: { name: true }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/location/query.getNameById.schema.ts b/packages/api/router/location/query.getNameById.schema.ts new file mode 100644 index 0000000000..c8e527d51d --- /dev/null +++ b/packages/api/router/location/query.getNameById.schema.ts @@ -0,0 +1,6 @@ +import { type z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetNameByIdSchema = prefixedId('orgLocation') +export type TGetNameByIdSchema = z.infer diff --git a/packages/api/router/location/query.getNames.handler.ts b/packages/api/router/location/query.getNames.handler.ts new file mode 100644 index 0000000000..c9cdb6ef45 --- /dev/null +++ b/packages/api/router/location/query.getNames.handler.ts @@ -0,0 +1,23 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetNamesSchema } from './query.getNames.schema' + +export const getNames = async ({ input }: TRPCHandlerParams) => { + try { + const results = await prisma.orgLocation.findMany({ + where: { + organization: { id: input.organizationId }, + }, + select: { + id: true, + name: true, + }, + }) + + return results + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/location/query.getNames.schema.ts b/packages/api/router/location/query.getNames.schema.ts new file mode 100644 index 0000000000..cf6eae1366 --- /dev/null +++ b/packages/api/router/location/query.getNames.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetNamesSchema = z.object({ organizationId: prefixedId('organization') }) +export type TGetNamesSchema = z.infer diff --git a/packages/api/router/location/schemas.ts b/packages/api/router/location/schemas.ts new file mode 100644 index 0000000000..93aa7e3067 --- /dev/null +++ b/packages/api/router/location/schemas.ts @@ -0,0 +1,14 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.create.schema' +export * from './mutation.createMany.schema' +export * from './mutation.update.schema' +export * from './query.forGoogleMaps.schema' +export * from './query.forLocationCard.schema' +export * from './query.forLocationPage.schema' +export * from './query.forVisitCard.schema' +export * from './query.getAddress.schema' +export * from './query.getById.schema' +export * from './query.getByOrgId.schema' +export * from './query.getNameById.schema' +export * from './query.getNames.schema' +// codegen:end diff --git a/packages/api/router/location/selects.ts b/packages/api/router/location/selects.ts new file mode 100644 index 0000000000..0e4c590530 --- /dev/null +++ b/packages/api/router/location/selects.ts @@ -0,0 +1,291 @@ +import { type Prisma } from '@weareinreach/db' +import { type Context } from '~api/lib' +import { globalSelect, globalWhere } from '~api/selects/global' + +export const select = { + country(): Prisma.CountryDefaultArgs { + return { + select: { + cca2: true, + cca3: true, + id: true, + name: true, + dialCode: true, + flag: true, + tsKey: true, + tsNs: true, + }, + } + }, + govDistBasic(): Prisma.GovDistDefaultArgs { + return { + select: { + govDistType: { + select: { + tsNs: true, + tsKey: true, + }, + }, + tsKey: true, + tsNs: true, + abbrev: true, + }, + } + }, + govDistExpanded(): Prisma.GovDistDefaultArgs { + return { + select: { + id: true, + name: true, + slug: true, + iso: true, + abbrev: true, + country: this.country(), + govDistType: { + select: { + tsKey: true, + tsNs: true, + }, + }, + isPrimary: true, + tsKey: true, + tsNs: true, + parent: { + select: { + id: true, + name: true, + slug: true, + iso: true, + abbrev: true, + country: this.country(), + govDistType: { + select: { + tsKey: true, + tsNs: true, + }, + }, + isPrimary: true, + tsKey: true, + tsNs: true, + }, + }, + }, + } + }, + attributes(): Prisma.LocationAttributeDefaultArgs { + return { + select: { + attribute: { + select: { + id: true, + tsKey: true, + tsNs: true, + icon: true, + iconBg: true, + showOnLocation: true, + categories: { + select: { + category: { + select: { + tag: true, + icon: true, + }, + }, + }, + }, + _count: { + select: { + parents: true, + children: true, + }, + }, + }, + }, + supplement: { + select: { + id: true, + country: this.country(), + language: { + select: { + languageName: true, + nativeName: true, + }, + }, + text: globalSelect.freeText(), + govDist: this.govDistBasic(), + boolean: true, + data: true, + }, + }, + }, + } + }, + orgEmail(): Prisma.OrgLocationEmailDefaultArgs { + return { + select: { + email: { + select: { + title: { + select: { + tsKey: true, + tsNs: true, + }, + }, + firstName: true, + lastName: true, + email: true, + legacyDesc: true, + description: globalSelect.freeText(), + primary: true, + locationOnly: true, + serviceOnly: true, + }, + }, + }, + } + }, + language(): Prisma.LanguageDefaultArgs { + return { + select: { + languageName: true, + nativeName: true, + }, + } + }, + orgWebsite(): Prisma.OrgWebsiteDefaultArgs { + return { + select: { + id: true, + description: globalSelect.freeText(), + languages: { select: { language: this.language() } }, + url: true, + isPrimary: true, + orgLocationId: true, + orgLocationOnly: true, + }, + } + }, + orgPhone(): Prisma.OrgLocationPhoneDefaultArgs { + return { + select: { + phone: { + select: { + country: this.country(), + phoneLangs: { + select: { + language: this.language(), + }, + }, + phoneType: { + select: { + tsKey: true, + tsNs: true, + }, + }, + description: globalSelect.freeText(), + number: true, + ext: true, + primary: true, + locationOnly: true, + }, + }, + }, + } + }, + orgPhoto(): Prisma.OrgPhotoDefaultArgs { + return { + select: { + src: true, + height: true, + width: true, + }, + } + }, + hours(): Prisma.OrgHoursDefaultArgs { + return { + select: { + dayIndex: true, + start: true, + end: true, + closed: true, + tz: true, + }, + } + }, + serviceTags(): Prisma.ServiceTagDefaultArgs { + return { + select: { + name: true, + tsKey: true, + tsNs: true, + category: { + select: { + tsKey: true, + tsNs: true, + }, + }, + defaultAttributes: { + select: { + attribute: { + select: { + categories: { select: { category: { select: { icon: true, ns: true, tag: true } } } }, + }, + }, + }, + }, + }, + } + }, + serviceArea(): Prisma.ServiceAreaDefaultArgs { + return { + select: { + countries: { select: { country: this.country() } }, + districts: { select: { govDist: this.govDistBasic() } }, + }, + } + }, + service(ctx: Context): Prisma.OrgLocationServiceDefaultArgs { + return { + select: { + service: { + select: { + serviceName: globalSelect.freeText(), + description: globalSelect.freeText(), + hours: this.hours(), + attributes: this.attributes(), + serviceAreas: this.serviceArea(), + services: { select: { tag: this.serviceTags() } }, + accessDetails: this.attributes(), + reviews: { where: { visible: true, deleted: false }, select: { id: true } }, + phones: { where: { phone: globalWhere.isPublic() }, ...this.orgPhone() }, + emails: { where: { email: globalWhere.isPublic() }, ...this.orgEmail() }, + userLists: ctx.session?.user.id + ? { + where: { list: { ownedById: ctx.session.user.id } }, + select: { list: { select: { id: true, name: true } } }, + } + : false, + id: true, + }, + }, + }, + } + }, + socialMedia(): Prisma.OrgSocialMediaDefaultArgs { + return { + select: { + service: { + select: { + tsKey: true, + tsNs: true, + logoIcon: true, + name: true, + urlBase: true, + }, + }, + url: true, + username: true, + }, + } + }, +} From 71207988f8b34ae85184245d5f579694efc920d8 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Fri, 4 Aug 2023 20:29:06 -0400 Subject: [PATCH 08/63] create shells --- packages/api/router/location/index.ts | 7 ++ .../location/mutation.create.handler.ts | 7 ++ .../router/location/mutation.create.schema.ts | 97 +++++++++++++++++++ .../location/mutation.createMany.handler.ts | 7 ++ .../location/mutation.createMany.schema.ts | 4 + .../location/mutation.update.handler.ts | 7 ++ .../router/location/mutation.update.schema.ts | 4 + 7 files changed, 133 insertions(+) create mode 100644 packages/api/router/location/mutation.create.handler.ts create mode 100644 packages/api/router/location/mutation.create.schema.ts create mode 100644 packages/api/router/location/mutation.createMany.handler.ts create mode 100644 packages/api/router/location/mutation.createMany.schema.ts create mode 100644 packages/api/router/location/mutation.update.handler.ts create mode 100644 packages/api/router/location/mutation.update.schema.ts diff --git a/packages/api/router/location/index.ts b/packages/api/router/location/index.ts index 72fbd74f64..ab7232791a 100644 --- a/packages/api/router/location/index.ts +++ b/packages/api/router/location/index.ts @@ -2,5 +2,12 @@ import { mergeRouters } from '~api/lib/trpc' import { mutations } from './mutations' import { queries } from './queries' +// import * as schema from './schemas' + +export const HandlerCache: Partial = {} export const locationRouter = mergeRouters(queries, mutations) + +type LocationHandlerCache = { + getById: typeof import('./query.getById.handler').getById +} diff --git a/packages/api/router/location/mutation.create.handler.ts b/packages/api/router/location/mutation.create.handler.ts new file mode 100644 index 0000000000..166af30acb --- /dev/null +++ b/packages/api/router/location/mutation.create.handler.ts @@ -0,0 +1,7 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateSchema } from './mutation.create.schema' + +export const create = async ({ ctx, input }: TRPCHandlerParams) => {} diff --git a/packages/api/router/location/mutation.create.schema.ts b/packages/api/router/location/mutation.create.schema.ts new file mode 100644 index 0000000000..82a4aad3fd --- /dev/null +++ b/packages/api/router/location/mutation.create.schema.ts @@ -0,0 +1,97 @@ +import { z } from 'zod' + +import { createGeoFields, Prisma } from '@weareinreach/db' +import { CreateBase } from '~api/schemaBase/create' +import { AuditLogSchema } from '~api/schemas/create/auditLog' +import { prefixedId } from '~api/schemas/idPrefix' +import { createManyRequired } from '~api/schemas/nestedOps' + +export const ZCreateSchema = () => { + const { dataParser: parser, inputSchema } = CreateBase( + z.object({ + orgId: prefixedId('organization'), + id: prefixedId('orgLocation').optional(), + name: z.string().optional(), + street1: z.string(), + street2: z.string().optional(), + city: z.string(), + postCode: z.string().optional(), + primary: z.boolean().optional(), + govDistId: z.string(), + countryId: z.string(), + longitude: z.number(), + latitude: z.number(), + published: z.boolean().default(false), + emails: z + .object({ orgEmailId: prefixedId('orgEmail') }) + .array() + .optional(), + phones: z + .object({ phoneId: prefixedId('orgPhone') }) + .array() + .optional(), + services: z + .object({ serviceId: prefixedId('orgService') }) + .array() + .optional(), + }) + ) + const dataParser = parser.transform(({ actorId, data }) => { + const { emails, phones, services, ...dataTo } = data + const linkAuditLogs: Prisma.AuditLogUncheckedCreateWithoutOrgLocationInput[] = [] + const serviceLinks = ( + !services + ? undefined + : createManyRequired( + services.map(({ serviceId }) => { + linkAuditLogs.push(AuditLogSchema.parse({ actorId, operation: 'LINK', serviceId })) + return { serviceId } + }) + ) + ) satisfies Prisma.OrgLocationServiceCreateNestedManyWithoutLocationInput | undefined + + const emailLinks = ( + !emails + ? undefined + : createManyRequired( + emails.map(({ orgEmailId }) => { + linkAuditLogs.push(AuditLogSchema.parse({ actorId, operation: 'LINK', orgEmailId })) + return { orgEmailId } + }) + ) + ) satisfies Prisma.OrgLocationEmailCreateNestedManyWithoutLocationInput | undefined + const phoneLinks = ( + !phones + ? undefined + : createManyRequired( + phones.map(({ phoneId }) => { + linkAuditLogs.push(AuditLogSchema.parse({ actorId, operation: 'LINK', phoneId })) + return { phoneId } + }) + ) + ) satisfies Prisma.OrgLocationPhoneCreateNestedManyWithoutLocationInput | undefined + const auditEntry = AuditLogSchema.parse({ + actorId, + operation: 'CREATE', + to: dataTo, + }) + + return Prisma.validator()({ + data: { + ...dataTo, + ...createGeoFields({ longitude: dataTo.longitude, latitude: dataTo.latitude }), + emails: emailLinks, + phones: phoneLinks, + services: serviceLinks, + auditLogs: { + createMany: { + data: [auditEntry, ...linkAuditLogs], + }, + }, + }, + }) + }) + + return { dataParser, inputSchema } +} +export type TCreateSchema = z.infer['inputSchema']> diff --git a/packages/api/router/location/mutation.createMany.handler.ts b/packages/api/router/location/mutation.createMany.handler.ts new file mode 100644 index 0000000000..e7b696f901 --- /dev/null +++ b/packages/api/router/location/mutation.createMany.handler.ts @@ -0,0 +1,7 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateManySchema } from './mutation.createMany.schema' + +export const createMany = async ({ ctx, input }: TRPCHandlerParams) => {} diff --git a/packages/api/router/location/mutation.createMany.schema.ts b/packages/api/router/location/mutation.createMany.schema.ts new file mode 100644 index 0000000000..fd75c09865 --- /dev/null +++ b/packages/api/router/location/mutation.createMany.schema.ts @@ -0,0 +1,4 @@ +import { z } from 'zod' + +export const ZCreateManySchema = z.literal('change me') +export type TCreateManySchema = z.infer diff --git a/packages/api/router/location/mutation.update.handler.ts b/packages/api/router/location/mutation.update.handler.ts new file mode 100644 index 0000000000..c53cf7cbcf --- /dev/null +++ b/packages/api/router/location/mutation.update.handler.ts @@ -0,0 +1,7 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUpdateSchema } from './mutation.update.schema' + +export const update = async ({ ctx, input }: TRPCHandlerParams) => {} diff --git a/packages/api/router/location/mutation.update.schema.ts b/packages/api/router/location/mutation.update.schema.ts new file mode 100644 index 0000000000..61be5c2424 --- /dev/null +++ b/packages/api/router/location/mutation.update.schema.ts @@ -0,0 +1,4 @@ +import { z } from 'zod' + +export const ZUpdateSchema = z.literal('change me') +export type TUpdateSchema = z.infer From 450b26d7edb39dc5b78905091dbd0f38434b9739 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Fri, 4 Aug 2023 20:30:15 -0400 Subject: [PATCH 09/63] common schemas, geo functions --- packages/api/schemaBase/create.ts | 12 ++++++++++ packages/api/schemaBase/createMany.ts | 10 ++++++++ packages/api/schemaBase/update.ts | 18 +++++++++++++++ packages/api/selects/global.ts | 26 +++++++++++++++++++++ packages/db/index.ts | 2 +- packages/db/lib/createPoint.ts | 33 ++++++++++++++++++++++++++- packages/db/zod_util/geojson.ts | 4 ++-- 7 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 packages/api/schemaBase/create.ts create mode 100644 packages/api/schemaBase/createMany.ts create mode 100644 packages/api/schemaBase/update.ts create mode 100644 packages/api/selects/global.ts diff --git a/packages/api/schemaBase/create.ts b/packages/api/schemaBase/create.ts new file mode 100644 index 0000000000..4fa57291de --- /dev/null +++ b/packages/api/schemaBase/create.ts @@ -0,0 +1,12 @@ +import { z } from 'zod' + +export const CreateBase = ( + schema: z.ZodObject> +) => ({ + dataParser: z.object({ + actorId: z.string(), + data: schema, + operation: z.enum(['CREATE', 'LINK', 'UNLINK']), + }), + inputSchema: schema, +}) diff --git a/packages/api/schemaBase/createMany.ts b/packages/api/schemaBase/createMany.ts new file mode 100644 index 0000000000..181963a996 --- /dev/null +++ b/packages/api/schemaBase/createMany.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' + +export const CreateManyBase = >(schema: T) => ({ + dataParser: z.object({ + actorId: z.string(), + data: schema, + operation: z.enum(['CREATE', 'LINK', 'UNLINK']), + }), + inputSchema: schema, +}) diff --git a/packages/api/schemaBase/update.ts b/packages/api/schemaBase/update.ts new file mode 100644 index 0000000000..defdc9f261 --- /dev/null +++ b/packages/api/schemaBase/update.ts @@ -0,0 +1,18 @@ +import { z } from 'zod' + +export const UpdateBase = ( + schema: z.ZodObject> +) => ({ + dataParser: z.object({ + actorId: z.string(), + from: schema.deepPartial().optional(), + to: schema, + operation: z.enum(['CREATE', 'UPDATE', 'DELETE', 'LINK', 'UNLINK']), + }), + inputSchema: z + .object({ + from: schema.deepPartial().optional(), + to: schema, + }) + .or(schema), +}) diff --git a/packages/api/selects/global.ts b/packages/api/selects/global.ts new file mode 100644 index 0000000000..a54d66d8ef --- /dev/null +++ b/packages/api/selects/global.ts @@ -0,0 +1,26 @@ +import { type Prisma } from '@weareinreach/db' + +export const globalSelect = { + freeText(): Prisma.FreeTextDefaultArgs { + return { + select: { + key: true, + ns: true, + tsKey: { + select: { + text: true, + }, + }, + }, + } + }, +} + +export const globalWhere = { + isPublic() { + return { + published: true, + deleted: false, + } as const + }, +} diff --git a/packages/db/index.ts b/packages/db/index.ts index 1a84ea1ff2..106cb4bf50 100644 --- a/packages/db/index.ts +++ b/packages/db/index.ts @@ -2,7 +2,7 @@ export * from './client' export * from './zod_util' export { slug } from './lib/slugGen' -export { createPoint } from './lib/createPoint' +export { createPoint, createPointOrNull, createGeoFields } from './lib/createPoint' export { generateFreeText, generateNestedFreeText } from './lib/generateFreeText' export { generateId, getIdPrefixRegex, isIdFor } from './lib/idGen' export { PrismaInstrumentation } from '@prisma/instrumentation' diff --git a/packages/db/lib/createPoint.ts b/packages/db/lib/createPoint.ts index 139e67d09d..2b18999f17 100644 --- a/packages/db/lib/createPoint.ts +++ b/packages/db/lib/createPoint.ts @@ -1,11 +1,42 @@ +import { geojsonToWKT } from '@terraformer/wkt' import { point } from '@turf/helpers' +import { z } from 'zod' + +import { Prisma } from '~db/client' +import { GeoJSONPointSchema, latitude, longitude } from '~db/zod_util' export const createPoint = ({ longitude, latitude }: CreatePointArgs) => { if (!longitude || !latitude) return 'JsonNull' return point([longitude, latitude]).geometry } +export const createPointOrNull = ({ longitude, latitude }: CreatePointArgs) => { + if (!longitude || !latitude) return Prisma.JsonNull + return point([longitude, latitude]).geometry +} + +const GeoSchema = z.object({ latitude, longitude }) + +export const createGeoFields = ({ longitude, latitude }: CreatePointArgs, opts?: CreateGeoFieldsOpts) => { + const parsed = GeoSchema.safeParse({ latitude, longitude }) + + if (!parsed.success) return {} -type CreatePointArgs = { + const geom = point([parsed.data.longitude, parsed.data.latitude]).geometry + + return { + ...(opts?.excludeLatLon ? {} : { latitude: parsed.data.latitude, longitude: parsed.data.longitude }), + ...(opts?.excludeWKT ? {} : { geoWKT: geojsonToWKT(geom) }), + ...(opts?.excludeGeoJSON ? {} : { geoJSON: GeoJSONPointSchema.parse(geom) }), + } +} + +interface CreatePointArgs { longitude: number | undefined latitude: number | undefined } + +interface CreateGeoFieldsOpts { + excludeLatLon?: boolean + excludeWKT?: boolean + excludeGeoJSON?: boolean +} diff --git a/packages/db/zod_util/geojson.ts b/packages/db/zod_util/geojson.ts index c5e70d049c..8038e0385b 100644 --- a/packages/db/zod_util/geojson.ts +++ b/packages/db/zod_util/geojson.ts @@ -2,9 +2,9 @@ import { type Prisma } from '@prisma/client' import { z } from 'zod' /** Longitudes are vertical lines that measure east or west of the meridian in Greenwich, England */ -const longitude = z.number().gte(-180).lte(180) +export const longitude = z.number().gte(-180).lte(180) /** Latitudes are horizontal lines that measure distance north or south of the equator. */ -const latitude = z.number().gte(-90).lte(90) +export const latitude = z.number().gte(-90).lte(90) /** [Longitude, Latitude] */ const coordTuple = z.tuple([longitude, latitude]) const polygon = coordTuple.array().min(4).array() From 76cc0eb76e5ed7fd3d0debf32b2452f51f29ec45 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Fri, 4 Aug 2023 20:30:57 -0400 Subject: [PATCH 10/63] connect on load -> speed up cold start?? --- packages/db/client/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/db/client/index.ts b/packages/db/client/index.ts index 6250bc00d1..b9a4e7aad3 100644 --- a/packages/db/client/index.ts +++ b/packages/db/client/index.ts @@ -54,7 +54,7 @@ if (!global.prisma) { prisma.$on('warn', (event) => log.warn(event)) } } - +prisma.$connect() if (process.env.NODE_ENV !== 'production') { global.prisma = prisma } From f43bfad8e5a9e4dcd027b0ff5f7a46160f99ccab Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Fri, 4 Aug 2023 20:44:14 -0400 Subject: [PATCH 11/63] disable autoconnect --- packages/db/client/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/db/client/index.ts b/packages/db/client/index.ts index b9a4e7aad3..ede7fabcef 100644 --- a/packages/db/client/index.ts +++ b/packages/db/client/index.ts @@ -54,7 +54,7 @@ if (!global.prisma) { prisma.$on('warn', (event) => log.warn(event)) } } -prisma.$connect() +// prisma.$connect() if (process.env.NODE_ENV !== 'production') { global.prisma = prisma } From bfb40450d8af658625675f00581cc967639d85bf Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 11:32:35 -0400 Subject: [PATCH 12/63] fix lockfile --- pnpm-lock.yaml | 193 ++++++++----------------------------------------- 1 file changed, 31 insertions(+), 162 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7420afd585..a5e4fcf3e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1521,12 +1521,12 @@ importers: '@storybook/addon-actions': specifier: 7.2.1 version: 7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-designs': + specifier: 7.0.1 + version: 7.0.1(@storybook/addon-docs@7.2.1)(@storybook/addons@7.2.1)(@storybook/components@7.2.1)(@storybook/manager-api@7.2.1)(@storybook/preview-api@7.2.1)(@storybook/theming@7.2.1)(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-docs': specifier: 7.2.1 version: 7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) - '@storybook/addon-designs': - specifier: 7.0.1 - version: 7.0.1(@storybook/addon-docs@7.2.0)(@storybook/addons@7.2.0)(@storybook/components@7.2.0)(@storybook/manager-api@7.2.0)(@storybook/preview-api@7.2.0)(@storybook/theming@7.2.0)(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-essentials': specifier: 7.2.1 version: 7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) @@ -9059,6 +9059,34 @@ packages: - supports-color dev: true + /@storybook/addon-designs@7.0.1(@storybook/addon-docs@7.2.1)(@storybook/addons@7.2.1)(@storybook/components@7.2.1)(@storybook/manager-api@7.2.1)(@storybook/preview-api@7.2.1)(@storybook/theming@7.2.1)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-bp7eoOYnoKp48H7tx0nkhhi9y7qUBr2YE9pd2DdxlS+4s2REglVeh1bNbBEYg9QUKPeKgUoWLMonLMTuCUyLoQ==} + peerDependencies: + '@storybook/addon-docs': ^7.0.0 + '@storybook/addons': ^7.0.0 + '@storybook/components': ^7.0.0 || 7 + '@storybook/manager-api': ^7.0.0 + '@storybook/preview-api': ^7.0.0 + '@storybook/theming': ^7.0.0 || 7 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + dependencies: + '@figspec/react': 1.0.3(react@18.2.0) + '@storybook/addon-docs': 7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + '@storybook/addons': 7.2.1(react-dom@18.2.0)(react@18.2.0) + '@storybook/components': 7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + '@storybook/manager-api': 7.2.1(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.2.1 + '@storybook/theming': 7.2.1(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /@storybook/addon-docs@7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-QlUM22wK0cE9glMRt1auP3BccjafdRvcsAnaLvDIL12HRaUqMpH6vvNN3A3MXo6XuzbOmDwAov5mXdCenpz02A==} peerDependencies: @@ -9290,27 +9318,6 @@ packages: - '@types/react-dom' dev: true - /@storybook/addons@6.5.16(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-p3DqQi+8QRL5k7jXhXmJZLsE/GqHqyY6PcoA1oNTJr0try48uhTGUOYkgzmqtDaa/qPFO5LP+xCPzZXckGtquQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - dependencies: - '@storybook/api': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/channels': 6.5.16 - '@storybook/client-logger': 6.5.16 - '@storybook/core-events': 6.5.16 - '@storybook/csf': 0.0.2--canary.4566f4d.1 - '@storybook/router': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/theming': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@types/webpack-env': 1.18.0 - core-js: 3.30.0 - global: 4.4.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - regenerator-runtime: 0.13.11 - dev: true - /@storybook/addons@7.2.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-I49JOSU5Imv91IWiNQvqGJDZ/llnSxQvttWAh+exhw7+Pq3xfwRmD+fNfyqm1C68/dVGNgwY/w40AP5cQb2PLA==} peerDependencies: @@ -9324,33 +9331,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/api@6.5.16(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-HOsuT8iomqeTMQJrRx5U8nsC7lJTwRr1DhdD0SzlqL4c80S/7uuCy4IZvOt4sYQjOzW5fOo/kamcoBXyLproTA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - dependencies: - '@storybook/channels': 6.5.16 - '@storybook/client-logger': 6.5.16 - '@storybook/core-events': 6.5.16 - '@storybook/csf': 0.0.2--canary.4566f4d.1 - '@storybook/router': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/semver': 7.3.2 - '@storybook/theming': 6.5.16(react-dom@18.2.0)(react@18.2.0) - core-js: 3.30.0 - fast-deep-equal: 3.1.3 - global: 4.4.0 - lodash: 4.17.21 - memoizerific: 1.11.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - regenerator-runtime: 0.13.11 - store2: 2.14.2 - telejson: 6.0.8 - ts-dedent: 2.2.0 - util-deprecate: 1.0.2 - dev: true - /@storybook/blocks@7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-1fPsFC6n9R267KwxGHiL80OuIdMDRC9QuIW4sRF0tF/G/yvucbofySYRQl/Y8LjsMJq8D4NpG5xLsneSxMP5cg==} peerDependencies: @@ -9481,14 +9461,6 @@ packages: - webpack-cli dev: true - /@storybook/channels@6.5.16: - resolution: {integrity: sha512-VylzaWQZaMozEwZPJdyJoz+0jpDa8GRyaqu9TGG6QGv+KU5POoZaGLDkRE7TzWkyyP0KQLo80K99MssZCpgSeg==} - dependencies: - core-js: 3.30.0 - ts-dedent: 2.2.0 - util-deprecate: 1.0.2 - dev: true - /@storybook/channels@7.2.1: resolution: {integrity: sha512-3ZogzjwlFG+oarwnI7TTvWvHVOUtJbjrgZkM5QuLMlxNzIR1XuBY8f01yf4K8+VpdNy9DY+7Q/j6tBThfwYvpA==} dependencies: @@ -9558,13 +9530,6 @@ packages: '@storybook/preview-api': 7.2.1 dev: true - /@storybook/client-logger@6.5.16: - resolution: {integrity: sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==} - dependencies: - core-js: 3.30.0 - global: 4.4.0 - dev: true - /@storybook/client-logger@7.2.1: resolution: {integrity: sha512-Lyht/lJg2S65CXRy9rXAZXP/Mgye7jbi/aqQL8z9VRMGChbL+k/3pSZnXTTrD1OVSpCEr4UWA+9bStzT4VjtYA==} dependencies: @@ -9652,12 +9617,6 @@ packages: - supports-color dev: true - /@storybook/core-events@6.5.16: - resolution: {integrity: sha512-qMZQwmvzpH5F2uwNUllTPg6eZXr2OaYZQRRN8VZJiuorZzDNdAFmiVWMWdkThwmyLEJuQKXxqCL8lMj/7PPM+g==} - dependencies: - core-js: 3.30.0 - dev: true - /@storybook/core-events@7.2.1: resolution: {integrity: sha512-EUXYb3gyQ2EzpDAWkgfoDl1EPabj3OE6+zntsD/gwvzQU85BTocs10ksnRyS55bfrQpYbf+Z+gw2CZboyagLgg==} dev: true @@ -10091,21 +10050,6 @@ packages: - supports-color dev: true - /@storybook/router@6.5.16(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-ZgeP8a5YV/iuKbv31V8DjPxlV4AzorRiR8OuSt/KqaiYXNXlOoQDz/qMmiNcrshrfLpmkzoq7fSo4T8lWo2UwQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - dependencies: - '@storybook/client-logger': 6.5.16 - core-js: 3.30.0 - memoizerific: 1.11.3 - qs: 6.11.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - regenerator-runtime: 0.13.11 - dev: true - /@storybook/router@7.2.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-9Cn5boUS+7yhrKlSy1kt7ruNs/znk3555kclBD6+uuhH/dD84feGeiGYE4GUuLmcKrDFtNF185/Gr1huJ556tA==} peerDependencies: @@ -10119,15 +10063,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/semver@7.3.2: - resolution: {integrity: sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==} - engines: {node: '>=10'} - hasBin: true - dependencies: - core-js: 3.30.0 - find-up: 4.1.0 - dev: true - /@storybook/store@7.2.1: resolution: {integrity: sha512-0EBD3GMCZpVk2y/Yzk7koH91Ny+kyq+krWaGFgZQ8JrGouuw1JAszxzgO1VVgnRio88UKeK3UALx9hGSHqDXeg==} dependencies: @@ -10200,20 +10135,6 @@ packages: ts-dedent: 2.2.0 dev: true - /@storybook/theming@6.5.16(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-hNLctkjaYLRdk1+xYTkC1mg4dYz2wSv6SqbLpcKMbkPHTE0ElhddGPHQqB362md/w9emYXNkt1LSMD8Xk9JzVQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - dependencies: - '@storybook/client-logger': 6.5.16 - core-js: 3.30.0 - memoizerific: 1.11.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - regenerator-runtime: 0.13.11 - dev: true - /@storybook/theming@7.2.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-cfnNCLvKmzxjmoYKfLl7Q64gSTouLvd23CtvBAOlWcRYnMJ9v4/7A2tK3xQyVRlJYh9OuXiHFLL8AXbN58Hkzw==} peerDependencies: @@ -13850,31 +13771,6 @@ packages: dependencies: type-fest: 1.4.0 - /css-chaos-addon@0.0.5(@storybook/addons@6.5.16)(@storybook/api@6.5.16)(@storybook/components@7.2.1)(@storybook/core-events@7.2.1)(@storybook/theming@7.2.1)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-h9AravpeFHIPAHcYVNyHTph7Jv3GGviiurddlIv2aY3Zu0SwQa567MWBxgZVLc5KhEX4qUCADbuJLycEKsjcGg==} - peerDependencies: - '@storybook/addons': ^6.5.8 - '@storybook/api': ^6.5.8 - '@storybook/components': ^6.5.8 || 7 - '@storybook/core-events': ^6.5.8 || 7 - '@storybook/theming': ^6.5.8 || 7 - react: ^16.8.0 || ^17.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - dependencies: - '@storybook/addons': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/api': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/components': 7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) - '@storybook/core-events': 7.2.1 - '@storybook/theming': 7.2.1(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: true - /css-loader@6.8.1(webpack@5.88.2): resolution: {integrity: sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==} engines: {node: '>= 12.13.0'} @@ -23434,33 +23330,6 @@ packages: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} dev: true - /storybook-addon-designs@7.0.0-beta.2(@storybook/addon-docs@7.2.1)(@storybook/addons@6.5.16)(@storybook/api@6.5.16)(@storybook/components@7.2.1)(@storybook/theming@7.2.1)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-ljBNmyCJdPTXhiBSfA1S+GBxtMooW2M7nxlt49OoCRH7jcxZOYQdiI8JYQiMF5Ur0MGakbSci0Xm+JzAvcm02g==} - deprecated: Newer versions of this package is available under `@storybook/addon-designs` - peerDependencies: - '@storybook/addon-docs': ^6.4.0 || ^7.0.0 - '@storybook/addons': ^6.4.0 || ^7.0.0 - '@storybook/api': ^6.4.0 || ^7.0.0 - '@storybook/components': ^6.4.0 || ^7.0.0 || 7 - '@storybook/theming': ^6.4.0 || ^7.0.0 || 7 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - dependencies: - '@figspec/react': 1.0.3(react@18.2.0) - '@storybook/addon-docs': 7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) - '@storybook/addons': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/api': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/components': 7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) - '@storybook/theming': 7.2.1(react-dom@18.2.0)(react@18.2.0) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: true - /storybook-addon-pseudo-states@2.1.0(@storybook/components@7.2.1)(@storybook/core-events@7.2.1)(@storybook/manager-api@7.2.1)(@storybook/preview-api@7.2.1)(@storybook/theming@7.2.1)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-AwbCL1OiZ16aIeXSP/IOovkMwXy7NTZqmjkz+UM2guSGjvogHNA95NhuVyWoqieE+QWUpGO48+MrBGMeeJcHOQ==} peerDependencies: From 9f03c7b9ba566b18a3429ed771eec3d96b213c7d Mon Sep 17 00:00:00 2001 From: InReach Bot <108850934+InReach-svc@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:35:29 +0000 Subject: [PATCH 13/63] chore: lint & format Signed-off-by: InReach Bot <108850934+InReach-svc@users.noreply.github.com> --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 0ae7aa0397..77de2c61a1 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -76,7 +76,7 @@ "@mantine/utils": "6.0.17", "@storybook/addon-a11y": "7.2.1", "@storybook/addon-actions": "7.2.1", - "@storybook/addon-designs": "7.0.1", + "@storybook/addon-designs": "7.0.1", "@storybook/addon-docs": "7.2.1", "@storybook/addon-essentials": "7.2.1", "@storybook/addon-interactions": "7.2.1", From b91be852bea4c74e28ac584ef6b575bfd9925e0e Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:03:11 -0400 Subject: [PATCH 14/63] location handler caching --- packages/api/router/location/index.ts | 131 +++++++++++++++++- .../location/mutation.create.handler.ts | 14 +- .../location/mutation.createMany.handler.ts | 7 - .../location/mutation.createMany.schema.ts | 4 - .../location/mutation.update.handler.ts | 34 ++++- .../router/location/mutation.update.schema.ts | 95 ++++++++++++- packages/api/router/location/mutations.ts | 78 ----------- packages/api/router/location/queries.ts | 97 ------------- packages/api/router/location/schemas.ts | 1 - 9 files changed, 265 insertions(+), 196 deletions(-) delete mode 100644 packages/api/router/location/mutation.createMany.handler.ts delete mode 100644 packages/api/router/location/mutation.createMany.schema.ts delete mode 100644 packages/api/router/location/mutations.ts delete mode 100644 packages/api/router/location/queries.ts diff --git a/packages/api/router/location/index.ts b/packages/api/router/location/index.ts index ab7232791a..3def314e1e 100644 --- a/packages/api/router/location/index.ts +++ b/packages/api/router/location/index.ts @@ -1,13 +1,134 @@ -import { mergeRouters } from '~api/lib/trpc' +import { defineRouter, permissionedProcedure, publicProcedure } from '~api/lib/trpc' -import { mutations } from './mutations' -import { queries } from './queries' -// import * as schema from './schemas' +import * as schema from './schemas' export const HandlerCache: Partial = {} -export const locationRouter = mergeRouters(queries, mutations) +export const locationRouter = defineRouter({ + // + // QUERIES + // + // #region Queries + + getById: publicProcedure.input(schema.ZGetByIdSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getById) { + HandlerCache.getById = await import('./query.getById.handler').then((mod) => mod.getById) + } + if (!HandlerCache.getById) throw new Error('Failed to load handler') + return HandlerCache.getById({ ctx, input }) + }), + getByOrgId: publicProcedure.input(schema.ZGetByOrgIdSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getByOrgId) { + HandlerCache.getByOrgId = await import('./query.getByOrgId.handler').then((mod) => mod.getByOrgId) + } + if (!HandlerCache.getByOrgId) throw new Error('Failed to load handler') + return HandlerCache.getByOrgId({ ctx, input }) + }), + getNameById: publicProcedure.input(schema.ZGetNameByIdSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getNameById) { + HandlerCache.getNameById = await import('./query.getNameById.handler').then((mod) => mod.getNameById) + } + if (!HandlerCache.getNameById) throw new Error('Failed to load handler') + return HandlerCache.getNameById({ ctx, input }) + }), + getNames: permissionedProcedure('getDetails') + .input(schema.ZGetNamesSchema) + .query(async ({ ctx, input }) => { + if (!HandlerCache.getNames) { + HandlerCache.getNames = await import('./query.getNames.handler').then((mod) => mod.getNames) + } + if (!HandlerCache.getNames) throw new Error('Failed to load handler') + return HandlerCache.getNames({ ctx, input }) + }), + getAddress: permissionedProcedure('updateLocation') + .input(schema.ZGetAddressSchema) + .query(async ({ ctx, input }) => { + if (!HandlerCache.getAddress) { + HandlerCache.getAddress = await import('./query.getAddress.handler').then((mod) => mod.getAddress) + } + if (!HandlerCache.getAddress) throw new Error('Failed to load handler') + return HandlerCache.getAddress({ ctx, input }) + }), + forLocationCard: publicProcedure.input(schema.ZForLocationCardSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forLocationCard) { + HandlerCache.forLocationCard = await import('./query.forLocationCard.handler').then( + (mod) => mod.forLocationCard + ) + } + if (!HandlerCache.forLocationCard) throw new Error('Failed to load handler') + return HandlerCache.forLocationCard({ ctx, input }) + }), + forVisitCard: publicProcedure.input(schema.ZForVisitCardSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forVisitCard) { + HandlerCache.forVisitCard = await import('./query.forVisitCard.handler').then((mod) => mod.forVisitCard) + } + if (!HandlerCache.forVisitCard) throw new Error('Failed to load handler') + return HandlerCache.forVisitCard({ ctx, input }) + }), + forGoogleMaps: publicProcedure.input(schema.ZForGoogleMapsSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forGoogleMaps) { + HandlerCache.forGoogleMaps = await import('./query.forGoogleMaps.handler').then( + (mod) => mod.forGoogleMaps + ) + } + if (!HandlerCache.forGoogleMaps) throw new Error('Failed to load handler') + return HandlerCache.forGoogleMaps({ ctx, input }) + }), + forLocationPage: publicProcedure.input(schema.ZForLocationPageSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forLocationPage) { + HandlerCache.forLocationPage = await import('./query.forLocationPage.handler').then( + (mod) => mod.forLocationPage + ) + } + if (!HandlerCache.forLocationPage) throw new Error('Failed to load handler') + return HandlerCache.forLocationPage({ ctx, input }) + }), + // #endregion + // + // MUTATIONS + // + // #region Mutations + create: permissionedProcedure('createNewLocation') + .input(schema.ZCreateSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.create) { + HandlerCache.create = await import('./mutation.create.handler').then((mod) => mod.create) + } + if (!HandlerCache.create) throw new Error('Failed to load handler') + return HandlerCache.create({ ctx, input }) + }), + update: permissionedProcedure('updateLocation') + .input(schema.ZUpdateSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.update) { + HandlerCache.update = await import('./mutation.update.handler').then((mod) => mod.update) + } + if (!HandlerCache.update) throw new Error('Failed to load handler') + return HandlerCache.update({ ctx, input }) + }), + // #endregion +}) type LocationHandlerCache = { + // + // QUERIES + // + // #region Queries getById: typeof import('./query.getById.handler').getById + getByOrgId: typeof import('./query.getByOrgId.handler').getByOrgId + getNameById: typeof import('./query.getNameById.handler').getNameById + getNames: typeof import('./query.getNames.handler').getNames + getAddress: typeof import('./query.getAddress.handler').getAddress + forLocationCard: typeof import('./query.forLocationCard.handler').forLocationCard + forVisitCard: typeof import('./query.forVisitCard.handler').forVisitCard + forGoogleMaps: typeof import('./query.forGoogleMaps.handler').forGoogleMaps + forLocationPage: typeof import('./query.forLocationPage.handler').forLocationPage + // #endregion + // + // MUTATIONS + // + // #region Mutations + create: typeof import('./mutation.create.handler').create + update: typeof import('./mutation.update.handler').update + // #endregion } diff --git a/packages/api/router/location/mutation.create.handler.ts b/packages/api/router/location/mutation.create.handler.ts index 166af30acb..a60c9aeb29 100644 --- a/packages/api/router/location/mutation.create.handler.ts +++ b/packages/api/router/location/mutation.create.handler.ts @@ -2,6 +2,16 @@ import { prisma } from '@weareinreach/db' import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' -import { type TCreateSchema } from './mutation.create.schema' +import { type TCreateSchema, ZCreateSchema } from './mutation.create.schema' -export const create = async ({ ctx, input }: TRPCHandlerParams) => {} +export const create = async ({ input }: TRPCHandlerParams) => { + try { + const data = ZCreateSchema().dataParser.parse(input) + + const result = await prisma.orgLocation.create(data) + + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/location/mutation.createMany.handler.ts b/packages/api/router/location/mutation.createMany.handler.ts deleted file mode 100644 index e7b696f901..0000000000 --- a/packages/api/router/location/mutation.createMany.handler.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' -import { type TRPCHandlerParams } from '~api/types/handler' - -import { type TCreateManySchema } from './mutation.createMany.schema' - -export const createMany = async ({ ctx, input }: TRPCHandlerParams) => {} diff --git a/packages/api/router/location/mutation.createMany.schema.ts b/packages/api/router/location/mutation.createMany.schema.ts deleted file mode 100644 index fd75c09865..0000000000 --- a/packages/api/router/location/mutation.createMany.schema.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { z } from 'zod' - -export const ZCreateManySchema = z.literal('change me') -export type TCreateManySchema = z.infer diff --git a/packages/api/router/location/mutation.update.handler.ts b/packages/api/router/location/mutation.update.handler.ts index c53cf7cbcf..774ff7a430 100644 --- a/packages/api/router/location/mutation.update.handler.ts +++ b/packages/api/router/location/mutation.update.handler.ts @@ -1,7 +1,39 @@ +import { TRPCError } from '@trpc/server' + import { prisma } from '@weareinreach/db' import { handleError } from '~api/lib/errorHandler' +import { updateGeo } from '~api/lib/prismaRaw/updateGeo' +import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TUpdateSchema } from './mutation.update.schema' -export const update = async ({ ctx, input }: TRPCHandlerParams) => {} +export const update = async ({ ctx, input }: TRPCHandlerParams) => { + try { + if (ctx.session === null || !ctx.session?.user.id) throw new TRPCError({ code: 'UNAUTHORIZED' }) + + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgLocation.findUniqueOrThrow({ where }) + const auditLog = CreateAuditLog({ + actorId: ctx.session!.user.id, + operation: 'UPDATE', + from: current, + to: data, + }) + const update = await tx.orgLocation.update({ + where, + data: { ...data, auditLogs: auditLog }, + select: { id: true }, + }) + + // if WKT is updated, we need to have the DB update the `geo` column with raw SQL. + if (data.geoWKT) await updateGeo('orgLocation', where.id, tx) + + return update + }) + return updatedRecord + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/location/mutation.update.schema.ts b/packages/api/router/location/mutation.update.schema.ts index 61be5c2424..3e11ed5493 100644 --- a/packages/api/router/location/mutation.update.schema.ts +++ b/packages/api/router/location/mutation.update.schema.ts @@ -1,4 +1,97 @@ import { z } from 'zod' -export const ZUpdateSchema = z.literal('change me') +import { Geometry, Prisma } from '@weareinreach/db' +import { allAttributes } from '@weareinreach/db/generated/allAttributes' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUpdateSchema = z + .object({ + id: prefixedId('orgLocation'), + data: z + .object({ + name: z.string(), + street1: z.string(), + street2: z.string().nullable(), + city: z.string(), + postCode: z.string().nullable(), + primary: z.boolean(), + mailOnly: z.boolean().nullable(), + longitude: z.number().nullable(), + latitude: z.number().nullable(), + geoJSON: Geometry, + geoWKT: z.string().nullable(), + published: z.boolean(), + deleted: z.boolean(), + checkMigration: z.boolean(), + accessible: z.object({ + supplementId: prefixedId('attributeSupplement').optional(), + boolean: z.boolean().nullish(), + }), + countryId: prefixedId('country').nullable(), + govDistId: prefixedId('govDist').nullable(), + services: z.string().array(), + }) + .partial(), + }) + .transform(({ id, data }) => { + const { accessible, countryId, govDistId, services, mailOnly, ...rest } = data + + const accessibleAttrId = allAttributes.find(({ tag }) => tag === 'wheelchair-accessible')?.id + + const updateAccessibility = accessible?.boolean !== undefined && accessibleAttrId + + return Prisma.validator()({ + where: { id }, + data: { + ...rest, + mailOnly: mailOnly === false ? null : mailOnly, + ...(updateAccessibility + ? accessible.boolean !== null + ? { + attributes: { + upsert: { + where: { locationId_attributeId: { locationId: id, attributeId: accessibleAttrId } }, + create: { + attribute: { connect: { id: accessibleAttrId } }, + supplement: { create: { boolean: accessible.boolean } }, + }, + update: { + supplement: { + upsert: { + where: { id: accessible.supplementId ?? '' }, + create: { boolean: accessible.boolean }, + update: { boolean: accessible.boolean }, + }, + }, + }, + }, + }, + } + : { + attributes: { + delete: { locationId_attributeId: { locationId: id, attributeId: accessibleAttrId } }, + }, + } + : {}), + ...(countryId + ? { + country: { connect: { id: countryId } }, + } + : {}), + ...(govDistId + ? { + govDist: { connect: { id: govDistId } }, + } + : {}), + ...(services + ? { + services: { + createMany: { data: services.map((serviceId) => ({ serviceId })), skipDuplicates: true }, + deleteMany: { NOT: { serviceId: { in: services } } }, + }, + } + : {}), + }, + }) + }) export type TUpdateSchema = z.infer diff --git a/packages/api/router/location/mutations.ts b/packages/api/router/location/mutations.ts deleted file mode 100644 index 436e108cb8..0000000000 --- a/packages/api/router/location/mutations.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { handleError } from '~api/lib/errorHandler' -import { updateGeo } from '~api/lib/prismaRaw/updateGeo' -import { defineRouter, permissionedProcedure } from '~api/lib/trpc' -import { CreateAuditLog } from '~api/schemas/create/auditLog' -import { - CreateManyOrgLocationSchema, - CreateOrgLocationSchema, - EditOrgLocationSchema, -} from '~api/schemas/create/orgLocation' - -export const mutations = defineRouter({ - create: permissionedProcedure('createNewLocation') - .input(CreateOrgLocationSchema().inputSchema) - .mutation(async ({ ctx, input }) => { - try { - const data = CreateOrgLocationSchema().dataParser.parse(input) - - const result = await ctx.prisma.orgLocation.create(data) - - return result - } catch (error) { - handleError(error) - } - }), - createMany: permissionedProcedure('createManyNewLocation') - .input(CreateManyOrgLocationSchema().inputSchema) - .mutation(async ({ ctx, input }) => { - try { - const records = CreateManyOrgLocationSchema().dataParser.parse({ - actorId: ctx.session.user.id, - operation: 'CREATE', - data: input, - }) - const result = await ctx.prisma.$transaction(async (tx) => { - const locations = await tx.orgLocation.createMany({ - data: records.data, - skipDuplicates: true, - }) - const auditLogs = await tx.auditLog.createMany({ - data: records.auditLogs, - skipDuplicates: true, - }) - return { - locations: locations.count, - auditLogs: auditLogs.count, - } - }) - return result - } catch (error) { - handleError(error) - } - }), - update: permissionedProcedure('updateLocation') - .input(EditOrgLocationSchema) - .mutation(async ({ ctx, input }) => { - const { where, data } = input - const updatedRecord = await ctx.prisma.$transaction(async (tx) => { - const current = await tx.orgLocation.findUniqueOrThrow({ where }) - const auditLog = CreateAuditLog({ - actorId: ctx.session.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const update = await tx.orgLocation.update({ - where, - data: { ...data, auditLogs: auditLog }, - select: { id: true }, - }) - - // if WKT is updated, we need to have the DB update the `geo` column with raw SQL. - if (data.geoWKT) await updateGeo('orgLocation', where.id, tx) - - return update - }) - return updatedRecord - }), -}) diff --git a/packages/api/router/location/queries.ts b/packages/api/router/location/queries.ts deleted file mode 100644 index c003de8ced..0000000000 --- a/packages/api/router/location/queries.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { z } from 'zod' - -import { handleError } from '~api/lib/errorHandler' -import { defineRouter, permissionedProcedure, publicProcedure } from '~api/lib/trpc' -import { id, orgId } from '~api/schemas/common' -import { attributes, freeText, isPublic } from '~api/schemas/selects/common' - -import * as schema from './schemas' - -export const HandlerCache: Partial = {} -type LocationHandlerCache = { - getById: typeof import('./query.getById.handler').getById - getByOrgId: typeof import('./query.getByOrgId.handler').getByOrgId - getNameById: typeof import('./query.getNameById.handler').getNameById - getNames: typeof import('./query.getNames.handler').getNames - getAddress: typeof import('./query.getAddress.handler').getAddress - forLocationCard: typeof import('./query.forLocationCard.handler').forLocationCard - forVisitCard: typeof import('./query.forVisitCard.handler').forVisitCard - forGoogleMaps: typeof import('./query.forGoogleMaps.handler').forGoogleMaps - forLocationPage: typeof import('./query.forLocationPage.handler').forLocationPage -} - -export const queries = defineRouter({ - getById: publicProcedure.input(schema.ZGetByIdSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.getById) { - HandlerCache.getById = await import('./query.getById.handler').then((mod) => mod.getById) - } - if (!HandlerCache.getById) throw new Error('Failed to load handler') - return HandlerCache.getById({ ctx, input }) - }), - getByOrgId: publicProcedure.input(schema.ZGetByOrgIdSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.getByOrgId) { - HandlerCache.getByOrgId = await import('./query.getByOrgId.handler').then((mod) => mod.getByOrgId) - } - if (!HandlerCache.getByOrgId) throw new Error('Failed to load handler') - return HandlerCache.getByOrgId({ ctx, input }) - }), - getNameById: publicProcedure.input(schema.ZGetNameByIdSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.getNameById) { - HandlerCache.getNameById = await import('./query.getNameById.handler').then((mod) => mod.getNameById) - } - if (!HandlerCache.getNameById) throw new Error('Failed to load handler') - return HandlerCache.getNameById({ ctx, input }) - }), - getNames: permissionedProcedure('getDetails') - .input(schema.ZGetNamesSchema) - .query(async ({ ctx, input }) => { - if (!HandlerCache.getNames) { - HandlerCache.getNames = await import('./query.getNames.handler').then((mod) => mod.getNames) - } - if (!HandlerCache.getNames) throw new Error('Failed to load handler') - return HandlerCache.getNames({ ctx, input }) - }), - getAddress: permissionedProcedure('updateLocation') - .input(schema.ZGetAddressSchema) - .query(async ({ ctx, input }) => { - if (!HandlerCache.getAddress) { - HandlerCache.getAddress = await import('./query.getAddress.handler').then((mod) => mod.getAddress) - } - if (!HandlerCache.getAddress) throw new Error('Failed to load handler') - return HandlerCache.getAddress({ ctx, input }) - }), - forLocationCard: publicProcedure.input(schema.ZForLocationCardSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.forLocationCard) { - HandlerCache.forLocationCard = await import('./query.forLocationCard.handler').then( - (mod) => mod.forLocationCard - ) - } - if (!HandlerCache.forLocationCard) throw new Error('Failed to load handler') - return HandlerCache.forLocationCard({ ctx, input }) - }), - forVisitCard: publicProcedure.input(schema.ZForVisitCardSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.forVisitCard) { - HandlerCache.forVisitCard = await import('./query.forVisitCard.handler').then((mod) => mod.forVisitCard) - } - if (!HandlerCache.forVisitCard) throw new Error('Failed to load handler') - return HandlerCache.forVisitCard({ ctx, input }) - }), - forGoogleMaps: publicProcedure.input(schema.ZForGoogleMapsSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.forGoogleMaps) { - HandlerCache.forGoogleMaps = await import('./query.forGoogleMaps.handler').then( - (mod) => mod.forGoogleMaps - ) - } - if (!HandlerCache.forGoogleMaps) throw new Error('Failed to load handler') - return HandlerCache.forGoogleMaps({ ctx, input }) - }), - forLocationPage: publicProcedure.input(schema.ZForLocationPageSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.forLocationPage) { - HandlerCache.forLocationPage = await import('./query.forLocationPage.handler').then( - (mod) => mod.forLocationPage - ) - } - if (!HandlerCache.forLocationPage) throw new Error('Failed to load handler') - return HandlerCache.forLocationPage({ ctx, input }) - }), -}) diff --git a/packages/api/router/location/schemas.ts b/packages/api/router/location/schemas.ts index 93aa7e3067..a7b3f5fd14 100644 --- a/packages/api/router/location/schemas.ts +++ b/packages/api/router/location/schemas.ts @@ -1,6 +1,5 @@ // codegen:start {preset: barrel, include: ./*.schema.ts} export * from './mutation.create.schema' -export * from './mutation.createMany.schema' export * from './mutation.update.schema' export * from './query.forGoogleMaps.schema' export * from './query.forLocationCard.schema' From 89fedfaf5b61ab0e97ea36b0ca82fcf91b39d475 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:19:53 -0400 Subject: [PATCH 15/63] extract out global selects --- .../router/location/query.getById.handler.ts | 14 +- .../location/query.getByOrgId.handler.ts | 14 +- packages/api/router/location/selects.ts | 175 +----------------- packages/api/selects/global.ts | 159 ++++++++++++++++ 4 files changed, 182 insertions(+), 180 deletions(-) diff --git a/packages/api/router/location/query.getById.handler.ts b/packages/api/router/location/query.getById.handler.ts index 533cb59e2a..86aecb8c5f 100644 --- a/packages/api/router/location/query.getById.handler.ts +++ b/packages/api/router/location/query.getById.handler.ts @@ -14,8 +14,8 @@ export const getById = async ({ ctx, input }: TRPCHandlerParams) ...globalWhere.isPublic(), }, select: { - govDist: select.govDistBasic(), - country: select.country(), + govDist: globalSelect.govDistBasic(), + country: globalSelect.country(), attributes: { where: { attribute: { @@ -32,14 +32,14 @@ export const getById = async ({ ctx, input }: TRPCHandlerParams) ...select.attributes(), }, emails: { where: { email: globalWhere.isPublic() }, ...select.orgEmail() }, - websites: { where: globalWhere.isPublic(), ...select.orgWebsite() }, + websites: { where: globalWhere.isPublic(), ...globalSelect.orgWebsite() }, phones: { where: { phone: globalWhere.isPublic() }, ...select.orgPhone() }, - photos: { where: globalWhere.isPublic(), ...select.orgPhoto() }, - hours: select.hours(), + photos: { where: globalWhere.isPublic(), ...globalSelect.orgPhoto() }, + hours: globalSelect.hours(), reviews: { where: { visible: true, deleted: false }, select: { id: true } }, services: { where: { service: globalWhere.isPublic() }, ...select.service(ctx) }, - serviceAreas: select.serviceArea(), - socialMedia: { where: globalWhere.isPublic(), ...select.socialMedia() }, + serviceAreas: globalSelect.serviceArea(), + socialMedia: { where: globalWhere.isPublic(), ...globalSelect.socialMedia() }, description: globalSelect.freeText(), name: true, street1: true, diff --git a/packages/api/router/location/query.getByOrgId.handler.ts b/packages/api/router/location/query.getByOrgId.handler.ts index 91e3f66f36..3e6790e104 100644 --- a/packages/api/router/location/query.getByOrgId.handler.ts +++ b/packages/api/router/location/query.getByOrgId.handler.ts @@ -16,8 +16,8 @@ export const getByOrgId = async ({ ctx, input }: TRPCHandlerParams Date: Mon, 7 Aug 2023 15:37:18 -0400 Subject: [PATCH 16/63] cache service handlers --- .../location/mutation.create.handler.ts | 2 +- .../location/mutation.update.handler.ts | 4 +- packages/api/router/service/index.ts | 241 +++++++++- ...mutation.attachServiceAttribute.handler.ts | 41 ++ .../mutation.attachServiceAttribute.schema.ts | 99 ++++ .../mutation.attachServiceTags.handler.ts | 27 ++ .../mutation.attachServiceTags.schema.ts | 31 ++ .../router/service/mutation.create.handler.ts | 19 + .../router/service/mutation.create.schema.ts | 49 ++ ...tation.createAccessInstructions.handler.ts | 37 ++ ...utation.createAccessInstructions.schema.ts | 100 ++++ .../mutation.createServiceArea.handler.ts | 40 ++ .../mutation.createServiceArea.schema.ts | 57 +++ .../service/mutation.linkEmails.handler.ts | 24 + .../service/mutation.linkEmails.schema.ts | 34 ++ .../service/mutation.linkPhones.handler.ts | 24 + .../service/mutation.linkPhones.schema.ts | 34 ++ .../router/service/mutation.update.handler.ts | 26 ++ .../router/service/mutation.update.schema.ts | 18 + packages/api/router/service/mutations.ts | 173 ------- packages/api/router/service/queries.ts | 432 ------------------ .../api/router/service/query.byId.handler.ts | 153 +++++++ .../api/router/service/query.byId.schema.ts | 6 + .../router/service/query.byOrgId.handler.ts | 51 +++ .../router/service/query.byOrgId.schema.ts | 8 + .../service/query.byOrgLocationId.handler.ts | 153 +++++++ .../service/query.byOrgLocationId.schema.ts | 8 + .../service/query.byUserListId.handler.ts | 156 +++++++ .../service/query.byUserListId.schema.ts | 8 + .../service/query.forServiceDrawer.handler.ts | 95 ++++ .../service/query.forServiceDrawer.schema.ts | 8 + .../query.forServiceEditDrawer.handler.ts | 107 +++++ .../query.forServiceEditDrawer.schema.ts | 6 + .../query.forServiceInfoCard.handler.ts | 51 +++ .../query.forServiceInfoCard.schema.ts | 9 + .../service/query.forServiceModal.handler.ts | 65 +++ .../service/query.forServiceModal.schema.ts | 6 + .../service/query.getFilterOptions.handler.ts | 37 ++ .../router/service/query.getNames.handler.ts | 42 ++ .../router/service/query.getNames.schema.ts | 9 + .../service/query.getOptions.handler.ts | 26 ++ .../service/query.getParentName.handler.ts | 33 ++ .../service/query.getParentName.schema.ts | 8 + packages/api/router/service/schemas.ts | 20 + packages/api/router/service/selects.ts | 51 +++ 45 files changed, 2015 insertions(+), 613 deletions(-) create mode 100644 packages/api/router/service/mutation.attachServiceAttribute.handler.ts create mode 100644 packages/api/router/service/mutation.attachServiceAttribute.schema.ts create mode 100644 packages/api/router/service/mutation.attachServiceTags.handler.ts create mode 100644 packages/api/router/service/mutation.attachServiceTags.schema.ts create mode 100644 packages/api/router/service/mutation.create.handler.ts create mode 100644 packages/api/router/service/mutation.create.schema.ts create mode 100644 packages/api/router/service/mutation.createAccessInstructions.handler.ts create mode 100644 packages/api/router/service/mutation.createAccessInstructions.schema.ts create mode 100644 packages/api/router/service/mutation.createServiceArea.handler.ts create mode 100644 packages/api/router/service/mutation.createServiceArea.schema.ts create mode 100644 packages/api/router/service/mutation.linkEmails.handler.ts create mode 100644 packages/api/router/service/mutation.linkEmails.schema.ts create mode 100644 packages/api/router/service/mutation.linkPhones.handler.ts create mode 100644 packages/api/router/service/mutation.linkPhones.schema.ts create mode 100644 packages/api/router/service/mutation.update.handler.ts create mode 100644 packages/api/router/service/mutation.update.schema.ts delete mode 100644 packages/api/router/service/mutations.ts delete mode 100644 packages/api/router/service/queries.ts create mode 100644 packages/api/router/service/query.byId.handler.ts create mode 100644 packages/api/router/service/query.byId.schema.ts create mode 100644 packages/api/router/service/query.byOrgId.handler.ts create mode 100644 packages/api/router/service/query.byOrgId.schema.ts create mode 100644 packages/api/router/service/query.byOrgLocationId.handler.ts create mode 100644 packages/api/router/service/query.byOrgLocationId.schema.ts create mode 100644 packages/api/router/service/query.byUserListId.handler.ts create mode 100644 packages/api/router/service/query.byUserListId.schema.ts create mode 100644 packages/api/router/service/query.forServiceDrawer.handler.ts create mode 100644 packages/api/router/service/query.forServiceDrawer.schema.ts create mode 100644 packages/api/router/service/query.forServiceEditDrawer.handler.ts create mode 100644 packages/api/router/service/query.forServiceEditDrawer.schema.ts create mode 100644 packages/api/router/service/query.forServiceInfoCard.handler.ts create mode 100644 packages/api/router/service/query.forServiceInfoCard.schema.ts create mode 100644 packages/api/router/service/query.forServiceModal.handler.ts create mode 100644 packages/api/router/service/query.forServiceModal.schema.ts create mode 100644 packages/api/router/service/query.getFilterOptions.handler.ts create mode 100644 packages/api/router/service/query.getNames.handler.ts create mode 100644 packages/api/router/service/query.getNames.schema.ts create mode 100644 packages/api/router/service/query.getOptions.handler.ts create mode 100644 packages/api/router/service/query.getParentName.handler.ts create mode 100644 packages/api/router/service/query.getParentName.schema.ts create mode 100644 packages/api/router/service/schemas.ts create mode 100644 packages/api/router/service/selects.ts diff --git a/packages/api/router/location/mutation.create.handler.ts b/packages/api/router/location/mutation.create.handler.ts index a60c9aeb29..249f950dbb 100644 --- a/packages/api/router/location/mutation.create.handler.ts +++ b/packages/api/router/location/mutation.create.handler.ts @@ -4,7 +4,7 @@ import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateSchema, ZCreateSchema } from './mutation.create.schema' -export const create = async ({ input }: TRPCHandlerParams) => { +export const create = async ({ input }: TRPCHandlerParams) => { try { const data = ZCreateSchema().dataParser.parse(input) diff --git a/packages/api/router/location/mutation.update.handler.ts b/packages/api/router/location/mutation.update.handler.ts index 774ff7a430..4c306d703a 100644 --- a/packages/api/router/location/mutation.update.handler.ts +++ b/packages/api/router/location/mutation.update.handler.ts @@ -8,10 +8,8 @@ import { type TRPCHandlerParams } from '~api/types/handler' import { type TUpdateSchema } from './mutation.update.schema' -export const update = async ({ ctx, input }: TRPCHandlerParams) => { +export const update = async ({ ctx, input }: TRPCHandlerParams) => { try { - if (ctx.session === null || !ctx.session?.user.id) throw new TRPCError({ code: 'UNAUTHORIZED' }) - const { where, data } = input const updatedRecord = await prisma.$transaction(async (tx) => { const current = await tx.orgLocation.findUniqueOrThrow({ where }) diff --git a/packages/api/router/service/index.ts b/packages/api/router/service/index.ts index e7f30ef4ff..39eb3e5746 100644 --- a/packages/api/router/service/index.ts +++ b/packages/api/router/service/index.ts @@ -1,6 +1,239 @@ -import { mergeRouters } from '~api/lib/trpc' +import { defineRouter, permissionedProcedure, protectedProcedure, publicProcedure } from '~api/lib/trpc' -import { mutations } from './mutations' -import { queries } from './queries' +import * as schema from './schemas' -export const serviceRouter = mergeRouters(queries, mutations) +const HandlerCache: Partial = {} +export const serviceRouter = defineRouter({ + // + // QUERIES + // + // #region Queries + byId: publicProcedure.input(schema.ZByIdSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.byId) { + HandlerCache.byId = await import('./query.byId.handler').then((mod) => mod.byId) + } + if (!HandlerCache.byId) throw new Error('Failed to load handler') + return HandlerCache.byId({ ctx, input }) + }), + byOrgId: permissionedProcedure('updateOrgService') + .input(schema.ZByOrgIdSchema) + .query(async ({ ctx, input }) => { + if (!HandlerCache.byOrgId) { + HandlerCache.byOrgId = await import('./query.byOrgId.handler').then((mod) => mod.byOrgId) + } + if (!HandlerCache.byOrgId) throw new Error('Failed to load handler') + return HandlerCache.byOrgId({ ctx, input }) + }), + byOrgLocationId: publicProcedure.input(schema.ZByOrgLocationIdSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.byOrgLocationId) { + HandlerCache.byOrgLocationId = await import('./query.byOrgLocationId.handler').then( + (mod) => mod.byOrgLocationId + ) + } + if (!HandlerCache.byOrgLocationId) throw new Error('Failed to load handler') + return HandlerCache.byOrgLocationId({ ctx, input }) + }), + byUserListId: protectedProcedure.input(schema.ZByUserListIdSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.byUserListId) { + HandlerCache.byUserListId = await import('./query.byUserListId.handler').then((mod) => mod.byUserListId) + } + if (!HandlerCache.byUserListId) throw new Error('Failed to load handler') + return HandlerCache.byUserListId({ ctx, input }) + }), + getFilterOptions: publicProcedure.query(async () => { + if (!HandlerCache.getFilterOptions) { + HandlerCache.getFilterOptions = await import('./query.getFilterOptions.handler').then( + (mod) => mod.getFilterOptions + ) + } + if (!HandlerCache.getFilterOptions) throw new Error('Failed to load handler') + return HandlerCache.getFilterOptions() + }), + getParentName: publicProcedure.input(schema.ZGetParentNameSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getParentName) { + HandlerCache.getParentName = await import('./query.getParentName.handler').then( + (mod) => mod.getParentName + ) + } + if (!HandlerCache.getParentName) throw new Error('Failed to load handler') + return HandlerCache.getParentName({ ctx, input }) + }), + getNames: permissionedProcedure('getDetails') + .input(schema.ZGetNamesSchema) + .query(async ({ ctx, input }) => { + if (!HandlerCache.getNames) { + HandlerCache.getNames = await import('./query.getNames.handler').then((mod) => mod.getNames) + } + if (!HandlerCache.getNames) throw new Error('Failed to load handler') + return HandlerCache.getNames({ ctx, input }) + }), + forServiceDrawer: permissionedProcedure('updateOrgService') + .input(schema.ZForServiceDrawerSchema) + .query(async ({ ctx, input }) => { + if (!HandlerCache.forServiceDrawer) { + HandlerCache.forServiceDrawer = await import('./query.forServiceDrawer.handler').then( + (mod) => mod.forServiceDrawer + ) + } + if (!HandlerCache.forServiceDrawer) throw new Error('Failed to load handler') + return HandlerCache.forServiceDrawer({ ctx, input }) + }), + forServiceEditDrawer: permissionedProcedure('updateOrgService') + .input(schema.ZForServiceEditDrawerSchema) + .query(async ({ ctx, input }) => { + if (!HandlerCache.forServiceEditDrawer) { + HandlerCache.forServiceEditDrawer = await import('./query.forServiceEditDrawer.handler').then( + (mod) => mod.forServiceEditDrawer + ) + } + if (!HandlerCache.forServiceEditDrawer) throw new Error('Failed to load handler') + return HandlerCache.forServiceEditDrawer({ ctx, input }) + }), + getOptions: permissionedProcedure('updateOrgService').query(async () => { + if (!HandlerCache.getOptions) { + HandlerCache.getOptions = await import('./query.getOptions.handler').then((mod) => mod.getOptions) + } + if (!HandlerCache.getOptions) throw new Error('Failed to load handler') + return HandlerCache.getOptions() + }), + forServiceInfoCard: publicProcedure + .input(schema.ZForServiceInfoCardSchema) + .query(async ({ ctx, input }) => { + if (!HandlerCache.forServiceInfoCard) { + HandlerCache.forServiceInfoCard = await import('./query.forServiceInfoCard.handler').then( + (mod) => mod.forServiceInfoCard + ) + } + if (!HandlerCache.forServiceInfoCard) throw new Error('Failed to load handler') + return HandlerCache.forServiceInfoCard({ ctx, input }) + }), + forServiceModal: publicProcedure.input(schema.ZForServiceModalSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forServiceModal) { + HandlerCache.forServiceModal = await import('./query.forServiceModal.handler').then( + (mod) => mod.forServiceModal + ) + } + if (!HandlerCache.forServiceModal) throw new Error('Failed to load handler') + return HandlerCache.forServiceModal({ ctx, input }) + }), + // #endregion + // + // MUTATIONS + // + // #region Mutations + + create: permissionedProcedure('createOrgService') + .input(schema.ZCreateSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.create) { + HandlerCache.create = await import('./mutation.create.handler').then((mod) => mod.create) + } + if (!HandlerCache.create) throw new Error('Failed to load handler') + return HandlerCache.create({ ctx, input }) + }), + update: permissionedProcedure('updateOrgService') + .input(schema.ZUpdateSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.update) { + HandlerCache.update = await import('./mutation.update.handler').then((mod) => mod.update) + } + if (!HandlerCache.update) throw new Error('Failed to load handler') + return HandlerCache.update({ ctx, input }) + }), + attachServiceAttribute: permissionedProcedure('attachServiceAttribute') + .input(schema.ZAttachServiceAttributeSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.attachServiceAttribute) { + HandlerCache.attachServiceAttribute = await import('./mutation.attachServiceAttribute.handler').then( + (mod) => mod.attachServiceAttribute + ) + } + if (!HandlerCache.attachServiceAttribute) throw new Error('Failed to load handler') + return HandlerCache.attachServiceAttribute({ ctx, input }) + }), + attachServiceTags: permissionedProcedure('attachServiceTags') + .input(schema.ZAttachServiceTagsSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.attachServiceTags) { + HandlerCache.attachServiceTags = await import('./mutation.attachServiceTags.handler').then( + (mod) => mod.attachServiceTags + ) + } + if (!HandlerCache.attachServiceTags) throw new Error('Failed to load handler') + return HandlerCache.attachServiceTags({ ctx, input }) + }), + createServiceArea: permissionedProcedure('createServiceArea') + .input(schema.ZCreateServiceAreaSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.createServiceArea) { + HandlerCache.createServiceArea = await import('./mutation.createServiceArea.handler').then( + (mod) => mod.createServiceArea + ) + } + if (!HandlerCache.createServiceArea) throw new Error('Failed to load handler') + return HandlerCache.createServiceArea({ ctx, input }) + }), + linkEmails: permissionedProcedure('linkServiceEmail') + .input(schema.ZLinkEmailsSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.linkEmails) { + HandlerCache.linkEmails = await import('./mutation.linkEmails.handler').then((mod) => mod.linkEmails) + } + if (!HandlerCache.linkEmails) throw new Error('Failed to load handler') + return HandlerCache.linkEmails({ ctx, input }) + }), + linkPhones: permissionedProcedure('linkServicePhone') + .input(schema.ZLinkPhonesSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.linkPhones) { + HandlerCache.linkPhones = await import('./mutation.linkPhones.handler').then((mod) => mod.linkPhones) + } + if (!HandlerCache.linkPhones) throw new Error('Failed to load handler') + return HandlerCache.linkPhones({ ctx, input }) + }), + createAccessInstructions: permissionedProcedure('createAccessInstructions') + .input(schema.ZCreateAccessInstructionsSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.createAccessInstructions) { + HandlerCache.createAccessInstructions = await import( + './mutation.createAccessInstructions.handler' + ).then((mod) => mod.createAccessInstructions) + } + if (!HandlerCache.createAccessInstructions) throw new Error('Failed to load handler') + return HandlerCache.createAccessInstructions({ ctx, input }) + }), + // #endregion +}) + +type ServiceHandlerCache = { + // + // QUERIES + // + // #region Queries + byId: typeof import('./query.byId.handler').byId + byOrgId: typeof import('./query.byOrgId.handler').byOrgId + byOrgLocationId: typeof import('./query.byOrgLocationId.handler').byOrgLocationId + byUserListId: typeof import('./query.byUserListId.handler').byUserListId + getFilterOptions: typeof import('./query.getFilterOptions.handler').getFilterOptions + getParentName: typeof import('./query.getParentName.handler').getParentName + getNames: typeof import('./query.getNames.handler').getNames + forServiceDrawer: typeof import('./query.forServiceDrawer.handler').forServiceDrawer + forServiceEditDrawer: typeof import('./query.forServiceEditDrawer.handler').forServiceEditDrawer + getOptions: typeof import('./query.getOptions.handler').getOptions + forServiceInfoCard: typeof import('./query.forServiceInfoCard.handler').forServiceInfoCard + forServiceModal: typeof import('./query.forServiceModal.handler').forServiceModal + // #endregion + // + // MUTATIONS + // + // #region Mutations + create: typeof import('./mutation.create.handler').create + update: typeof import('./mutation.update.handler').update + attachServiceAttribute: typeof import('./mutation.attachServiceAttribute.handler').attachServiceAttribute + attachServiceTags: typeof import('./mutation.attachServiceTags.handler').attachServiceTags + createServiceArea: typeof import('./mutation.createServiceArea.handler').createServiceArea + linkEmails: typeof import('./mutation.linkEmails.handler').linkEmails + linkPhones: typeof import('./mutation.linkPhones.handler').linkPhones + createAccessInstructions: typeof import('./mutation.createAccessInstructions.handler').createAccessInstructions + // #endregion +} diff --git a/packages/api/router/service/mutation.attachServiceAttribute.handler.ts b/packages/api/router/service/mutation.attachServiceAttribute.handler.ts new file mode 100644 index 0000000000..49de9c14b6 --- /dev/null +++ b/packages/api/router/service/mutation.attachServiceAttribute.handler.ts @@ -0,0 +1,41 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { + type TAttachServiceAttributeSchema, + ZAttachServiceAttributeSchema, +} from './mutation.attachServiceAttribute.schema' + +export const attachServiceAttribute = async ({ + ctx, + input, +}: TRPCHandlerParams) => { + try { + const inputData = { + actorId: ctx.actorId, + operation: 'CREATE', + data: input, + } + const { attributeSupplement, auditLogs, freeText, serviceAttribute, translationKey } = + ZAttachServiceAttributeSchema().dataParser.parse(inputData) + + const result = await prisma.$transaction(async (tx) => { + const tKey = translationKey ? await tx.translationKey.create(translationKey) : undefined + const fText = freeText ? await tx.freeText.create(freeText) : undefined + const aSupp = attributeSupplement ? await tx.attributeSupplement.create(attributeSupplement) : undefined + const attrLink = await tx.serviceAttribute.create(serviceAttribute) + const logs = await tx.auditLog.createMany({ data: auditLogs, skipDuplicates: true }) + return { + translationKey: tKey, + freeText: fText, + attributeSupplement: aSupp, + serviceAttribute: attrLink, + auditLog: logs, + } + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/mutation.attachServiceAttribute.schema.ts b/packages/api/router/service/mutation.attachServiceAttribute.schema.ts new file mode 100644 index 0000000000..f9a9ecb39e --- /dev/null +++ b/packages/api/router/service/mutation.attachServiceAttribute.schema.ts @@ -0,0 +1,99 @@ +import { z } from 'zod' + +import { generateFreeText, generateId, InputJsonValue, Prisma } from '@weareinreach/db' +import { CreateBase } from '~api/schemaBase/create' +import { GenerateAuditLog } from '~api/schemas/create/auditLog' + +export const ZAttachServiceAttributeSchema = () => { + const { dataParser: parser, inputSchema } = CreateBase( + z.object({ + orgId: z.string(), + orgServiceId: z.string(), + attributeId: z.string(), + supplement: z + .object({ + data: InputJsonValue.optional(), + boolean: z.boolean().optional(), + countryId: z.string().optional(), + govDistId: z.string().optional(), + languageId: z.string().optional(), + text: z.string().optional(), + }) + .optional(), + }) + ) + + const dataParser = parser.transform(({ actorId, data: parsedData }) => { + const { orgId, orgServiceId, attributeId, supplement: supplementInput } = parsedData + + const supplementId = supplementInput ? generateId('attributeSupplement') : undefined + + const { freeText, translationKey } = + supplementId && supplementInput?.text + ? generateFreeText({ orgId, text: supplementInput.text, type: 'attSupp', itemId: supplementId }) + : { freeText: undefined, translationKey: undefined } + + const { boolean, countryId, data, govDistId, languageId } = supplementInput ?? {} + const auditLogs = new Set() + + if (freeText && translationKey) { + auditLogs.add( + GenerateAuditLog({ + actorId, + operation: 'CREATE', + freeTextId: freeText.id, + to: translationKey, + translationKey: translationKey.key, + }) + ) + } + + const supplementData = supplementInput + ? { id: supplementId, countryId, boolean, data, govDistId, languageId, textId: freeText?.id } + : undefined + if (supplementData) + auditLogs.add( + GenerateAuditLog({ + actorId, + operation: 'CREATE', + to: supplementData, + attributeSupplementId: supplementData.id, + attributeId, + }) + ) + + auditLogs.add( + GenerateAuditLog({ + actorId, + operation: 'LINK', + orgServiceId, + attributeId, + attributeSupplementId: supplementData?.id, + }) + ) + + return { + freeText: freeText ? Prisma.validator()({ data: freeText }) : undefined, + translationKey: translationKey + ? Prisma.validator()({ data: translationKey }) + : undefined, + attributeSupplement: supplementData + ? Prisma.validator()({ + data: supplementData, + }) + : undefined, + serviceAttribute: Prisma.validator()({ + data: { + attribute: { connect: { id: attributeId } }, + service: { connect: { id: orgServiceId } }, + supplement: supplementId ? { connect: { id: supplementId } } : undefined, + }, + }), + auditLogs: Array.from(auditLogs.values()), + } + }) + return { dataParser, inputSchema } +} +export type TAttachServiceAttributeSchema = z.infer< + ReturnType['inputSchema'] +> diff --git a/packages/api/router/service/mutation.attachServiceTags.handler.ts b/packages/api/router/service/mutation.attachServiceTags.handler.ts new file mode 100644 index 0000000000..8e1461fc70 --- /dev/null +++ b/packages/api/router/service/mutation.attachServiceTags.handler.ts @@ -0,0 +1,27 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TAttachServiceTagsSchema, ZAttachServiceTagsSchema } from './mutation.attachServiceTags.schema' + +export const attachServiceTags = async ({ + ctx, + input, +}: TRPCHandlerParams) => { + try { + const inputData = { + actorId: ctx.actorId, + operation: 'LINK', + to: input, + } + const { auditLogs, orgServiceTag } = ZAttachServiceTagsSchema().dataParser.parse(inputData) + const results = await prisma.$transaction(async (tx) => { + const tags = await tx.orgServiceTag.createMany(orgServiceTag) + const logs = await tx.auditLog.createMany(auditLogs) + return { orgServiceTag: tags.count, auditLog: logs.count } + }) + return results + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/mutation.attachServiceTags.schema.ts b/packages/api/router/service/mutation.attachServiceTags.schema.ts new file mode 100644 index 0000000000..ec283d89bc --- /dev/null +++ b/packages/api/router/service/mutation.attachServiceTags.schema.ts @@ -0,0 +1,31 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { CreateManyBase } from '~api/schemaBase/createMany' +import { GenerateAuditLog } from '~api/schemas/create/auditLog' + +export const ZAttachServiceTagsSchema = () => { + const { dataParser: parser, inputSchema } = CreateManyBase( + z + .object({ + serviceId: z.string(), + tagId: z.string(), + }) + .array() + ) + + const dataParser = parser.transform(({ actorId, data: parsedData }) => { + const auditLogs = parsedData.map(({ serviceId, tagId }) => + GenerateAuditLog({ actorId, operation: 'LINK', orgServiceId: serviceId, serviceTagId: tagId }) + ) + return { + orgServiceTag: Prisma.validator()({ + data: parsedData, + skipDuplicates: true, + }), + auditLogs: Prisma.validator()({ data: auditLogs, skipDuplicates: true }), + } + }) + return { dataParser, inputSchema } +} +export type TAttachServiceTagsSchema = z.infer['inputSchema']> diff --git a/packages/api/router/service/mutation.create.handler.ts b/packages/api/router/service/mutation.create.handler.ts new file mode 100644 index 0000000000..e952a7340f --- /dev/null +++ b/packages/api/router/service/mutation.create.handler.ts @@ -0,0 +1,19 @@ +import { TRPCError } from '@trpc/server' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateSchema, ZCreateSchema } from './mutation.create.schema' + +export const create = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const inputData = { actorId: ctx.session.user.id, operation: 'CREATE', data: input } + + const record = ZCreateSchema().dataParser.parse(inputData) + const result = await prisma.orgService.create(record) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/mutation.create.schema.ts b/packages/api/router/service/mutation.create.schema.ts new file mode 100644 index 0000000000..bedeba356e --- /dev/null +++ b/packages/api/router/service/mutation.create.schema.ts @@ -0,0 +1,49 @@ +import { z } from 'zod' + +import { generateId, generateNestedFreeText, Prisma } from '@weareinreach/db' +import { CreateBase } from '~api/schemaBase/create' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { connectOneId } from '~api/schemas/nestedOps' + +export const ZCreateSchema = () => { + const { dataParser: parser, inputSchema } = CreateBase( + z.object({ + orgId: z.string(), + data: z.object({ + serviceName: z.string(), + description: z.string().optional(), + organizationId: z.string(), + published: z.boolean().optional(), + }), + }) + ) + + const dataParser = parser.transform(({ data: parsedData, actorId, operation }) => { + const { orgId, data } = parsedData + const id = generateId('orgService') + const serviceName = generateNestedFreeText({ + orgId, + text: data.serviceName, + type: 'svcName', + itemId: id, + }) + const description = data.description + ? generateNestedFreeText({ orgId, text: data.description, type: 'svcDesc', itemId: id }) + : undefined + const organization = connectOneId(data.organizationId) + const { published } = data + + const recordData = { + id, + serviceName, + description, + organization, + published, + } + const auditLogs = CreateAuditLog({ actorId, operation, to: recordData }) + + return Prisma.validator()({ data: { ...recordData, auditLogs } }) + }) + return { dataParser, inputSchema } +} +export type TCreateSchema = z.infer['inputSchema']> diff --git a/packages/api/router/service/mutation.createAccessInstructions.handler.ts b/packages/api/router/service/mutation.createAccessInstructions.handler.ts new file mode 100644 index 0000000000..6077b23310 --- /dev/null +++ b/packages/api/router/service/mutation.createAccessInstructions.handler.ts @@ -0,0 +1,37 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { + type TCreateAccessInstructionsSchema, + ZCreateAccessInstructionsSchema, +} from './mutation.createAccessInstructions.schema' + +export const createAccessInstructions = async ({ + ctx, + input, +}: TRPCHandlerParams) => { + try { + const inputData = { actorId: ctx.actorId, operation: 'CREATE', data: input } + + const { serviceAccessAttribute, attributeSupplement, auditLogs, freeText, translationKey } = + ZCreateAccessInstructionsSchema().dataParser.parse(inputData) + const result = await prisma.$transaction(async (tx) => { + const tKey = translationKey ? await tx.translationKey.create(translationKey) : undefined + const fText = freeText ? await tx.freeText.create(freeText) : undefined + const aSupp = attributeSupplement ? await tx.attributeSupplement.create(attributeSupplement) : undefined + const attrLink = await tx.serviceAccessAttribute.create(serviceAccessAttribute) + const logs = await tx.auditLog.createMany({ data: auditLogs, skipDuplicates: true }) + return { + translationKey: tKey, + freeText: fText, + attributeSupplement: aSupp, + serviceAccessAttribute: attrLink, + auditLog: logs, + } + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/mutation.createAccessInstructions.schema.ts b/packages/api/router/service/mutation.createAccessInstructions.schema.ts new file mode 100644 index 0000000000..88fd6bdbbf --- /dev/null +++ b/packages/api/router/service/mutation.createAccessInstructions.schema.ts @@ -0,0 +1,100 @@ +import { z } from 'zod' + +import { generateFreeText, generateId, InputJsonValue, Prisma } from '@weareinreach/db' +import { CreateBase } from '~api/schemaBase/create' +import { GenerateAuditLog } from '~api/schemas/create/auditLog' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZCreateAccessInstructionsSchema = () => { + const { dataParser: parser, inputSchema } = CreateBase( + z.object({ + orgId: prefixedId('organization'), + serviceId: prefixedId('orgService'), + attributeId: prefixedId('attribute'), + supplement: z + .object({ + data: InputJsonValue.optional(), + boolean: z.boolean().optional(), + countryId: prefixedId('country').optional(), + govDistId: prefixedId('govDist').optional(), + languageId: prefixedId('language').optional(), + text: z.string().optional(), + }) + .optional(), + }) + ) + + const dataParser = parser.transform(({ actorId, data: parsedData }) => { + const { orgId, serviceId, attributeId, supplement: supplementInput } = parsedData + + const supplementId = supplementInput ? generateId('attributeSupplement') : undefined + + const { freeText, translationKey } = + supplementId && supplementInput?.text + ? generateFreeText({ orgId, text: supplementInput.text, type: 'attSupp', itemId: supplementId }) + : { freeText: undefined, translationKey: undefined } + + const { boolean, countryId, data, govDistId, languageId } = supplementInput ?? {} + const auditLogs = new Set() + + if (freeText && translationKey) { + auditLogs.add( + GenerateAuditLog({ + actorId, + operation: 'CREATE', + freeTextId: freeText.id, + to: translationKey, + translationKey: translationKey.key, + }) + ) + } + + const supplementData = supplementInput + ? { id: supplementId, countryId, boolean, data, govDistId, languageId, textId: freeText?.id } + : undefined + if (supplementData) + auditLogs.add( + GenerateAuditLog({ + actorId, + operation: 'CREATE', + to: supplementData, + attributeSupplementId: supplementData.id, + attributeId, + }) + ) + + auditLogs.add( + GenerateAuditLog({ + actorId, + operation: 'LINK', + attributeId, + attributeSupplementId: supplementData?.id, + }) + ) + + return { + freeText: freeText ? Prisma.validator()({ data: freeText }) : undefined, + translationKey: translationKey + ? Prisma.validator()({ data: translationKey }) + : undefined, + attributeSupplement: supplementData + ? Prisma.validator()({ + data: supplementData, + }) + : undefined, + + serviceAccessAttribute: Prisma.validator()({ + data: { + attribute: { connect: { id: attributeId } }, + service: { connect: { id: serviceId } }, + supplement: supplementId ? { connect: { id: supplementId } } : undefined, + }, + }), + auditLogs: Array.from(auditLogs.values()), + } + }) + return { dataParser, inputSchema } +} +export type TCreateAccessInstructionsSchema = z.infer< + ReturnType['inputSchema'] +> diff --git a/packages/api/router/service/mutation.createServiceArea.handler.ts b/packages/api/router/service/mutation.createServiceArea.handler.ts new file mode 100644 index 0000000000..dfafac6e1b --- /dev/null +++ b/packages/api/router/service/mutation.createServiceArea.handler.ts @@ -0,0 +1,40 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateServiceAreaSchema, ZCreateServiceAreaSchema } from './mutation.createServiceArea.schema' + +export const createServiceArea = async ({ + ctx, + input, +}: TRPCHandlerParams) => { + try { + const inputData = { + actorId: ctx.actorId, + operation: 'CREATE', + data: input, + } + const { serviceArea, auditLog, serviceAreaCountry, serviceAreaDist } = + ZCreateServiceAreaSchema().dataParser.parse(inputData) + const result = await prisma.$transaction(async (tx) => { + const area = await tx.serviceArea.create(serviceArea) + const countries = serviceAreaCountry + ? await tx.serviceAreaCountry.createMany({ data: serviceAreaCountry, skipDuplicates: true }) + : undefined + const districts = serviceAreaDist + ? await tx.serviceAreaDist.createMany({ data: serviceAreaDist, skipDuplicates: true }) + : undefined + const logs = await tx.auditLog.createMany({ data: auditLog, skipDuplicates: true }) + + return { + serviceArea: area.id, + auditLog: logs.count, + serviceAreaCountry: countries?.count ?? 0, + serviceAreaDist: districts?.count ?? 0, + } + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/mutation.createServiceArea.schema.ts b/packages/api/router/service/mutation.createServiceArea.schema.ts new file mode 100644 index 0000000000..48d125ec6f --- /dev/null +++ b/packages/api/router/service/mutation.createServiceArea.schema.ts @@ -0,0 +1,57 @@ +import flush from 'just-flush' +import { z } from 'zod' + +import { generateId, Prisma } from '@weareinreach/db' +import { CreateBase } from '~api/schemaBase/create' +import { GenerateAuditLog } from '~api/schemas/create/auditLog' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZCreateServiceAreaSchema = () => { + const { dataParser: parser, inputSchema } = CreateBase( + z.object({ + organizationId: prefixedId('organization').optional(), + orgServiceId: prefixedId('orgService').optional(), + orgLocationId: prefixedId('orgLocation').optional(), + countries: z.string().array().optional(), + districts: z.string().array().optional(), + }) + ) + + const dataParser = parser.transform(({ actorId, data: parsedData }) => { + const auditLog: Prisma.AuditLogUncheckedCreateInput[] = [] + const id = generateId('serviceArea') + const { orgLocationId, orgServiceId, organizationId } = parsedData + + const serviceAreaData = { id, orgLocationId, orgServiceId, organizationId } + const serviceArea = Prisma.validator()({ + data: serviceAreaData, + }) + auditLog.push(GenerateAuditLog({ actorId, operation: 'CREATE', to: serviceAreaData })) + const serviceAreaCountry: Prisma.ServiceAreaCountryUncheckedCreateInput[] | undefined = parsedData + .countries?.length + ? parsedData.countries.map((countryId) => { + auditLog.push(GenerateAuditLog({ actorId, operation: 'LINK', serviceAreaId: id, countryId })) + return { serviceAreaId: id, countryId } + }) + : undefined + const serviceAreaDist: Prisma.ServiceAreaDistUncheckedCreateInput[] | undefined = parsedData.districts + ?.length + ? parsedData.districts.map((govDistId) => { + auditLog.push(GenerateAuditLog({ actorId, operation: 'LINK', serviceAreaId: id, govDistId })) + return { serviceAreaId: id, govDistId } + }) + : undefined + + return { serviceArea, auditLog, serviceAreaCountry, serviceAreaDist } + }) + return { + dataParser, + inputSchema: inputSchema.refine((data) => { + const keys = Object.keys(flush(data)) + const ids = ['organizationId', 'orgServiceId', 'orgLocationId'] + const areas = ['countries', 'districts'] + return keys.some((key) => ids.includes(key)) && keys.some((key) => areas.includes(key)) + }), + } +} +export type TCreateServiceAreaSchema = z.infer['inputSchema']> diff --git a/packages/api/router/service/mutation.linkEmails.handler.ts b/packages/api/router/service/mutation.linkEmails.handler.ts new file mode 100644 index 0000000000..4163ad2681 --- /dev/null +++ b/packages/api/router/service/mutation.linkEmails.handler.ts @@ -0,0 +1,24 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TLinkEmailsSchema, ZLinkEmailsSchema } from './mutation.linkEmails.schema' + +export const linkEmails = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const inputData = { + actorId: ctx.actorId, + operation: 'CREATE', + data: input, + } + const { auditLog, orgServiceEmail } = ZLinkEmailsSchema().dataParser.parse(inputData) + const result = await prisma.$transaction(async (tx) => { + const links = await tx.orgServiceEmail.createMany({ data: orgServiceEmail, skipDuplicates: true }) + const logs = await tx.auditLog.createMany({ data: auditLog, skipDuplicates: true }) + return { orgServiceEmail: links.count, auditLog: logs.count } + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/mutation.linkEmails.schema.ts b/packages/api/router/service/mutation.linkEmails.schema.ts new file mode 100644 index 0000000000..a7980f9394 --- /dev/null +++ b/packages/api/router/service/mutation.linkEmails.schema.ts @@ -0,0 +1,34 @@ +import { z } from 'zod' + +import { type Prisma } from '@weareinreach/db' +import { CreateOneOrManyBase } from '~api/schemaBase/createOneOrMany' +import { GenerateAuditLog } from '~api/schemas/create/auditLog' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZLinkEmailsSchema = () => { + const { dataParser: parser, inputSchema } = CreateOneOrManyBase( + z.object({ + orgEmailId: prefixedId('orgEmail'), + serviceId: prefixedId('orgService'), + }) + ) + + const dataParser = parser.transform(({ actorId, data: parsedData }) => { + const auditLog: Prisma.AuditLogUncheckedCreateInput[] = [] + const orgServiceEmail: Prisma.OrgServiceEmailUncheckedCreateInput[] = [] + + if (Array.isArray(parsedData)) { + for (const { orgEmailId, serviceId } of parsedData) { + orgServiceEmail.push({ orgEmailId, serviceId }) + auditLog.push(GenerateAuditLog({ actorId, operation: 'LINK', orgEmailId, orgServiceId: serviceId })) + } + } else { + const { orgEmailId, serviceId } = parsedData + orgServiceEmail.push({ orgEmailId, serviceId }) + auditLog.push(GenerateAuditLog({ actorId, operation: 'LINK', orgEmailId, orgServiceId: serviceId })) + } + return { auditLog, orgServiceEmail } + }) + return { dataParser, inputSchema } +} +export type TLinkEmailsSchema = z.infer['inputSchema']> diff --git a/packages/api/router/service/mutation.linkPhones.handler.ts b/packages/api/router/service/mutation.linkPhones.handler.ts new file mode 100644 index 0000000000..bfced378fd --- /dev/null +++ b/packages/api/router/service/mutation.linkPhones.handler.ts @@ -0,0 +1,24 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TLinkPhonesSchema, ZLinkPhonesSchema } from './mutation.linkPhones.schema' + +export const linkPhones = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const inputData = { + actorId: ctx.actorId, + operation: 'CREATE', + data: input, + } + const { auditLog, orgServicePhone } = ZLinkPhonesSchema().dataParser.parse(inputData) + const result = await prisma.$transaction(async (tx) => { + const links = await tx.orgServicePhone.createMany({ data: orgServicePhone, skipDuplicates: true }) + const logs = await tx.auditLog.createMany({ data: auditLog, skipDuplicates: true }) + return { orgServicePhone: links.count, auditLog: logs.count } + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/mutation.linkPhones.schema.ts b/packages/api/router/service/mutation.linkPhones.schema.ts new file mode 100644 index 0000000000..fa9cdbad31 --- /dev/null +++ b/packages/api/router/service/mutation.linkPhones.schema.ts @@ -0,0 +1,34 @@ +import { z } from 'zod' + +import { type Prisma } from '@weareinreach/db' +import { CreateOneOrManyBase } from '~api/schemaBase/createOneOrMany' +import { GenerateAuditLog } from '~api/schemas/create/auditLog' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZLinkPhonesSchema = () => { + const { dataParser: parser, inputSchema } = CreateOneOrManyBase( + z.object({ + orgPhoneId: prefixedId('orgPhone'), + serviceId: prefixedId('orgService'), + }) + ) + + const dataParser = parser.transform(({ actorId, data: parsedData }) => { + const auditLog: Prisma.AuditLogUncheckedCreateInput[] = [] + const orgServicePhone: Prisma.OrgServicePhoneUncheckedCreateInput[] = [] + + if (Array.isArray(parsedData)) { + for (const { orgPhoneId, serviceId } of parsedData) { + orgServicePhone.push({ orgPhoneId, serviceId }) + auditLog.push(GenerateAuditLog({ actorId, operation: 'LINK', orgPhoneId, orgServiceId: serviceId })) + } + } else { + const { orgPhoneId, serviceId } = parsedData + orgServicePhone.push({ orgPhoneId, serviceId }) + auditLog.push(GenerateAuditLog({ actorId, operation: 'LINK', orgPhoneId, orgServiceId: serviceId })) + } + return { auditLog, orgServicePhone } + }) + return { dataParser, inputSchema } +} +export type TLinkPhonesSchema = z.infer['inputSchema']> diff --git a/packages/api/router/service/mutation.update.handler.ts b/packages/api/router/service/mutation.update.handler.ts new file mode 100644 index 0000000000..1468ab54d0 --- /dev/null +++ b/packages/api/router/service/mutation.update.handler.ts @@ -0,0 +1,26 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUpdateSchema } from './mutation.update.schema' + +export const update = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgService.findUniqueOrThrow({ where }) + const updated = await tx.orgService.update({ + where, + data: { + ...data, + auditLogs: CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from: current, to: data }), + }, + }) + return updated + }) + return updatedRecord + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/mutation.update.schema.ts b/packages/api/router/service/mutation.update.schema.ts new file mode 100644 index 0000000000..1bf560b615 --- /dev/null +++ b/packages/api/router/service/mutation.update.schema.ts @@ -0,0 +1,18 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUpdateSchema = z + .object({ + id: prefixedId('orgService'), + data: z + .object({ + published: z.boolean(), + deleted: z.boolean(), + checkMigration: z.boolean().nullable(), + }) + .partial(), + }) + .transform(({ id, data }) => Prisma.validator()({ where: { id }, data })) +export type TUpdateSchema = z.infer diff --git a/packages/api/router/service/mutations.ts b/packages/api/router/service/mutations.ts deleted file mode 100644 index 5289389a95..0000000000 --- a/packages/api/router/service/mutations.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { defineRouter, permissionedProcedure } from '~api/lib/trpc' -import { CreateAuditLog } from '~api/schemas/create/auditLog' -import { - AttachServAccess, - AttachServAttribute, - AttachServiceTags, - CreateOrgService, - CreateServiceArea, - CreateServiceAreaRefine, - LinkServiceEmail, - LinkServicePhone, - UpdateOrgService, -} from '~api/schemas/create/orgService' - -export const mutations = defineRouter({ - create: permissionedProcedure('createOrgService') - .input(CreateOrgService().inputSchema) - .mutation(async ({ ctx, input }) => { - const inputData = { actorId: ctx.session.user.id, operation: 'CREATE', data: input } - - const record = CreateOrgService().dataParser.parse(inputData) - const result = await ctx.prisma.orgService.create(record) - return result - }), - update: permissionedProcedure('updateOrgService') - .input(UpdateOrgService) - .mutation(async ({ ctx, input }) => { - const { where, data } = input - const updatedRecord = await ctx.prisma.$transaction(async (tx) => { - const current = await tx.orgService.findUniqueOrThrow({ where }) - const updated = await tx.orgService.update({ - where, - data: { - ...data, - auditLogs: CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from: current, to: data }), - }, - }) - return updated - }) - return updatedRecord - }), - attachServiceAttribute: permissionedProcedure('attachServiceAttribute') - .input(AttachServAttribute().inputSchema) - .mutation(async ({ ctx, input }) => { - const inputData = { - actorId: ctx.actorId, - operation: 'CREATE', - data: input, - } - const { attributeSupplement, auditLogs, freeText, serviceAttribute, translationKey } = - AttachServAttribute().dataParser.parse(inputData) - - const result = await ctx.prisma.$transaction(async (tx) => { - const tKey = translationKey ? await tx.translationKey.create(translationKey) : undefined - const fText = freeText ? await tx.freeText.create(freeText) : undefined - const aSupp = attributeSupplement - ? await tx.attributeSupplement.create(attributeSupplement) - : undefined - const attrLink = await tx.serviceAttribute.create(serviceAttribute) - const logs = await tx.auditLog.createMany({ data: auditLogs, skipDuplicates: true }) - return { - translationKey: tKey, - freeText: fText, - attributeSupplement: aSupp, - serviceAttribute: attrLink, - auditLog: logs, - } - }) - return result - }), - attachServiceTags: permissionedProcedure('attachServiceTags') - .input(AttachServiceTags().inputSchema) - .mutation(async ({ ctx, input }) => { - const inputData = { - actorId: ctx.actorId, - operation: 'LINK', - to: input, - } - const { auditLogs, orgServiceTag } = AttachServiceTags().dataParser.parse(inputData) - const results = await ctx.prisma.$transaction(async (tx) => { - const tags = await tx.orgServiceTag.createMany(orgServiceTag) - const logs = await tx.auditLog.createMany(auditLogs) - return { orgServiceTag: tags.count, auditLog: logs.count } - }) - return results - }), - createServiceArea: permissionedProcedure('createServiceArea') - .input(CreateServiceArea().inputSchema.refine(CreateServiceAreaRefine)) - .mutation(async ({ ctx, input }) => { - const inputData = { - actorId: ctx.actorId, - operation: 'CREATE', - data: input, - } - const { serviceArea, auditLog, serviceAreaCountry, serviceAreaDist } = - CreateServiceArea().dataParser.parse(inputData) - const result = await ctx.prisma.$transaction(async (tx) => { - const area = await tx.serviceArea.create(serviceArea) - const countries = serviceAreaCountry - ? await tx.serviceAreaCountry.createMany({ data: serviceAreaCountry, skipDuplicates: true }) - : undefined - const districts = serviceAreaDist - ? await tx.serviceAreaDist.createMany({ data: serviceAreaDist, skipDuplicates: true }) - : undefined - const logs = await tx.auditLog.createMany({ data: auditLog, skipDuplicates: true }) - - return { - serviceArea: area.id, - auditLog: logs.count, - serviceAreaCountry: countries?.count ?? 0, - serviceAreaDist: districts?.count ?? 0, - } - }) - return result - }), - linkEmails: permissionedProcedure('linkServiceEmail') - .input(LinkServiceEmail().inputSchema) - .mutation(async ({ ctx, input }) => { - const inputData = { - actorId: ctx.actorId, - operation: 'CREATE', - data: input, - } - const { auditLog, orgServiceEmail } = LinkServiceEmail().dataParser.parse(inputData) - const result = await ctx.prisma.$transaction(async (tx) => { - const links = await tx.orgServiceEmail.createMany({ data: orgServiceEmail, skipDuplicates: true }) - const logs = await tx.auditLog.createMany({ data: auditLog, skipDuplicates: true }) - return { orgServiceEmail: links.count, auditLog: logs.count } - }) - return result - }), - linkPhones: permissionedProcedure('linkServicePhone') - .input(LinkServicePhone().inputSchema) - .mutation(async ({ ctx, input }) => { - const inputData = { - actorId: ctx.actorId, - operation: 'CREATE', - data: input, - } - const { auditLog, orgServicePhone } = LinkServicePhone().dataParser.parse(inputData) - const result = await ctx.prisma.$transaction(async (tx) => { - const links = await tx.orgServicePhone.createMany({ data: orgServicePhone, skipDuplicates: true }) - const logs = await tx.auditLog.createMany({ data: auditLog, skipDuplicates: true }) - return { orgServicePhone: links.count, auditLog: logs.count } - }) - return result - }), - createAccessInstructions: permissionedProcedure('createAccessInstructions') - .input(AttachServAccess().inputSchema) - .mutation(async ({ ctx, input }) => { - const inputData = { actorId: ctx.actorId, operation: 'CREATE', data: input } - - const { serviceAccessAttribute, attributeSupplement, auditLogs, freeText, translationKey } = - AttachServAccess().dataParser.parse(inputData) - const result = await ctx.prisma.$transaction(async (tx) => { - const tKey = translationKey ? await tx.translationKey.create(translationKey) : undefined - const fText = freeText ? await tx.freeText.create(freeText) : undefined - const aSupp = attributeSupplement - ? await tx.attributeSupplement.create(attributeSupplement) - : undefined - const attrLink = await tx.serviceAccessAttribute.create(serviceAccessAttribute) - const logs = await tx.auditLog.createMany({ data: auditLogs, skipDuplicates: true }) - return { - translationKey: tKey, - freeText: fText, - attributeSupplement: aSupp, - serviceAccessAttribute: attrLink, - auditLog: logs, - } - }) - return result - }), -}) diff --git a/packages/api/router/service/queries.ts b/packages/api/router/service/queries.ts deleted file mode 100644 index 072b310674..0000000000 --- a/packages/api/router/service/queries.ts +++ /dev/null @@ -1,432 +0,0 @@ -import { TRPCError } from '@trpc/server' -import flush from 'just-flush' -import mapObjectVals from 'just-map-values' -import { z } from 'zod' - -import { getIdPrefixRegex, isIdFor } from '@weareinreach/db' -import { type Prisma } from '@weareinreach/db/client' -import { handleError } from '~api/lib/errorHandler' -import { transformer } from '~api/lib/transformer' -import { - defineRouter, - permissionedProcedure, - protectedProcedure, - publicProcedure, - // staffProcedure, -} from '~api/lib/trpc' -import { attributes, freeTextCrowdinId, isPublic } from '~api/schemas/selects/common' -import { - forServiceDrawer, - serviceById, - serviceByLocationId, - serviceByOrgId, - serviceByUserListId, -} from '~api/schemas/selects/orgService' - -export const queries = defineRouter({ - byId: publicProcedure.input(serviceById).query(async ({ ctx, input }) => { - try { - const result = await ctx.prisma.orgService.findUniqueOrThrow(input) - - return result - } catch (error) { - handleError(error) - } - }), - byOrgId: permissionedProcedure('updateOrgService') - .input(serviceByOrgId) - .query(async ({ ctx, input }) => { - try { - const results = await ctx.prisma.orgService.findMany(input) - return results - } catch (error) { - handleError(error) - } - }), - byOrgLocationId: publicProcedure.input(serviceByLocationId).query(async ({ ctx, input }) => { - try { - const results = await ctx.prisma.orgService.findMany(input) - return results - } catch (error) { - handleError(error) - } - }), - byUserListId: protectedProcedure.input(serviceByUserListId).query(async ({ ctx, input }) => { - try { - const { select, where } = input - const revisedInput = { - where: { - userLists: { - some: { - list: { - AND: { - id: where.userLists.some.listId, - ownedById: ctx.session.user.id, - }, - }, - }, - }, - }, - select, - } satisfies Prisma.OrgServiceFindManyArgs - - const results = await ctx.prisma.orgService.findMany(revisedInput) - return results - } catch (error) { - handleError(error) - } - }), - getFilterOptions: publicProcedure.query(async ({ ctx }) => { - const result = await ctx.prisma.serviceCategory.findMany({ - where: { - active: true, - OR: [{ crisisSupportOnly: null }, { crisisSupportOnly: false }], - }, - select: { - id: true, - tsKey: true, - tsNs: true, - services: { - where: { - active: true, - }, - select: { - id: true, - tsKey: true, - tsNs: true, - }, - orderBy: { - name: 'asc', - }, - }, - }, - orderBy: { - category: 'asc', - }, - }) - return result - }), - getParentName: publicProcedure - .input(z.object({ slug: z.string(), orgLocationId: z.string() }).partial()) - .query(async ({ ctx, input }) => { - const { slug, orgLocationId } = input - - switch (true) { - case Boolean(slug): { - return ctx.prisma.organization.findUniqueOrThrow({ - where: { slug }, - select: { name: true }, - }) - } - case Boolean(orgLocationId): { - return ctx.prisma.orgLocation.findUniqueOrThrow({ - where: { id: orgLocationId }, - select: { name: true }, - }) - } - default: { - throw new TRPCError({ code: 'BAD_REQUEST' }) - } - } - }), - getNames: permissionedProcedure('getDetails') - .input(z.object({ organizationId: z.string(), orgLocationId: z.string() }).partial()) - .query(async ({ ctx, input }) => { - const { orgLocationId, organizationId } = input - - if (!orgLocationId && !organizationId) throw new TRPCError({ code: 'BAD_REQUEST' }) - - const results = await ctx.prisma.orgService.findMany({ - where: { - organizationId: organizationId, - ...(orgLocationId - ? { - locations: { - some: { orgLocationId }, - }, - } - : {}), - }, - select: { - id: true, - serviceName: { select: { key: true, tsKey: { select: { text: true } } } }, - }, - }) - const transformedResults = flush( - results.map(({ id, serviceName }) => { - if (!serviceName) return - return { id, tsKey: serviceName.key, defaultText: serviceName.tsKey.text } - }) - ) - return transformedResults - }), - forServiceDrawer: permissionedProcedure('updateOrgService') - .input(forServiceDrawer) - .query(async ({ ctx, input }) => { - try { - type ServObj = { [k: string]: Set } - type ServItem = { - id: string - name: { - tsNs?: string - tsKey?: string - defaultText?: string - } - locations: (string | null)[] - attributes: { id: string; tsKey: string; tsNs: string }[] - } - - const results = await ctx.prisma.orgService.findMany(input) - let servObj: ServObj = {} - for (const service of results) { - servObj = service.services.reduce((items: ServObj, record) => { - const key = record.tag.category.tsKey - if (!items[key]) { - items[key] = new Set() - } - const itemToAdd = { - id: service.id, - name: { - tsNs: service.serviceName?.ns, - tsKey: service.serviceName?.key, - defaultText: service.serviceName?.tsKey.text, - }, - locations: service.locations.map(({ location }) => location.name), - attributes: service.attributes.map(({ attribute }) => { - const { id, tsKey, tsNs } = attribute - return { id, tsKey, tsNs } - }), - } satisfies ServItem - - items[key]?.add(transformer.stringify(itemToAdd)) - return items - }, servObj) - } - const transformed = mapObjectVals(servObj, (value) => - [...value].map((item) => transformer.parse(item)) - ) - return transformed - } catch (error) { - handleError(error) - } - }), - forServiceEditDrawer: permissionedProcedure('updateOrgService') - .input(z.string()) - .query(async ({ ctx, input }) => { - try { - const result = await ctx.prisma.orgService.findUniqueOrThrow({ - where: { id: input }, - select: { - id: true, - accessDetails: { - select: { - attribute: { select: { id: true, tsKey: true, tsNs: true } }, - supplement: { select: { id: true, text: freeTextCrowdinId, data: true } }, - }, - }, - attributes: { - select: { - attribute: { - select: { - id: true, - tsKey: true, - tsNs: true, - icon: true, - categories: { select: { category: { select: { tag: true } } } }, - }, - }, - supplement: { - select: { - id: true, - active: true, - data: true, - boolean: true, - countryId: true, - govDistId: true, - languageId: true, - text: freeTextCrowdinId, - }, - }, - }, - }, - description: freeTextCrowdinId, - phones: { select: { phone: { select: { id: true } } } }, - emails: { select: { email: { select: { id: true } } } }, - locations: { select: { location: { select: { id: true } } } }, - hours: { select: { id: true, dayIndex: true, start: true, end: true, closed: true, tz: true } }, - services: { select: { tag: { select: { id: true, categoryId: true } } } }, - serviceAreas: { - select: { - id: true, - countries: { select: { country: { select: { id: true } } } }, - districts: { select: { govDist: { select: { id: true } } } }, - }, - }, - published: true, - deleted: true, - serviceName: freeTextCrowdinId, - }, - }) - const { attributes, phones, emails, locations, services, serviceAreas, accessDetails, ...rest } = - result - const transformed = { - ...rest, - phones: phones.map(({ phone }) => phone.id), - emails: emails.map(({ email }) => email.id), - locations: locations.map(({ location }) => location.id), - services: services.map(({ tag }) => ({ id: tag.id, categoryId: tag.categoryId })), - serviceAreas: serviceAreas - ? { - id: serviceAreas.id, - countries: serviceAreas.countries.map(({ country }) => country.id), - districts: serviceAreas.districts.map(({ govDist }) => govDist.id), - } - : null, - attributes: attributes.map(({ attribute, supplement }) => { - const { categories, ...attr } = attribute - return { - attribute: { ...attr, categories: categories.map(({ category }) => category.tag) }, - supplement: supplement.map(({ data, ...rest }) => { - if (data) { - return { ...rest, data: transformer.parse(JSON.stringify(data)) } - } - return { ...rest, data } - }), - } - }), - accessDetails: accessDetails.map(({ attribute, supplement }) => ({ - attribute, - supplement: supplement.map(({ data, ...rest }) => { - if (data) { - return { ...rest, data: transformer.parse(JSON.stringify(data)) } - } - return { ...rest, data } - }), - })), - } - - return transformed - } catch (error) { - handleError(error) - } - }), - getOptions: permissionedProcedure('updateOrgService').query(async ({ ctx }) => { - const result = await ctx.prisma.serviceTag.findMany({ - select: { - id: true, - active: true, - tsKey: true, - tsNs: true, - category: { - select: { - id: true, - active: true, - tsKey: true, - tsNs: true, - }, - }, - }, - }) - return result - }), - forServiceInfoCard: publicProcedure - .input( - z.object({ - parentId: z.string().regex(getIdPrefixRegex('organization', 'orgLocation')), - remoteOnly: z.boolean().optional(), - }) - ) - .query(async ({ ctx, input }) => { - const result = await ctx.prisma.orgService.findMany({ - where: { - ...isPublic, - ...(isIdFor('organization', input.parentId) - ? { organization: { id: input.parentId, ...isPublic } } - : { locations: { some: { location: { id: input.parentId, ...isPublic } } } }), - ...(input.remoteOnly - ? { attributes: { some: { attribute: { active: true, tag: 'offers-remote-services' } } } } - : {}), - OR: [{ crisisSupportOnly: null }, { crisisSupportOnly: false }], - }, - select: { - id: true, - serviceName: { select: { key: true, ns: true, tsKey: { select: { text: true } } } }, - services: { - select: { - tag: { - select: { tsKey: true, category: { select: { tsKey: true } } }, - }, - }, - where: { tag: { active: true, category: { active: true } } }, - }, - attributes: { - where: { attribute: { active: true, tag: 'offers-remote-services' } }, - select: { attributeId: true }, - }, - }, - }) - - const transformed = result.map(({ id, serviceName, services, attributes }) => ({ - id, - serviceName: serviceName - ? { tsKey: serviceName.tsKey, tsNs: serviceName.ns, defaultText: serviceName.tsKey.text } - : null, - serviceCategories: [...new Set(services.map(({ tag }) => tag.category.tsKey))].sort(), - offersRemote: attributes.length > 0, - })) - return transformed - }), - forServiceModal: publicProcedure.input(z.string()).query(async ({ ctx, input }) => { - const result = await ctx.prisma.orgService.findUniqueOrThrow({ - where: { id: input, ...isPublic }, - select: { - id: true, - services: { select: { tag: { select: { tsKey: true } } }, where: { tag: { active: true } } }, - accessDetails: { - where: { active: true }, - - select: { - attribute: { select: { id: true } }, - supplement: { - where: { active: true }, - select: { - id: true, - data: true, - text: { select: { key: true, tsKey: { select: { text: true } } } }, - }, - }, - }, - }, - serviceName: { - select: { - key: true, - ns: true, - tsKey: { - select: { - text: true, - }, - }, - }, - }, - locations: { - where: { location: isPublic }, - select: { location: { select: { country: { select: { cca2: true } } } } }, - }, - attributes, - hours: { where: { active: true }, select: { _count: true } }, - description: { - select: { - key: true, - ns: true, - tsKey: { - select: { - text: true, - }, - }, - }, - }, - }, - }) - return result - }), -}) diff --git a/packages/api/router/service/query.byId.handler.ts b/packages/api/router/service/query.byId.handler.ts new file mode 100644 index 0000000000..c416aac48d --- /dev/null +++ b/packages/api/router/service/query.byId.handler.ts @@ -0,0 +1,153 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalSelect, globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TByIdSchema } from './query.byId.schema' +import { select } from './selects' + +export const byId = async ({ input }: TRPCHandlerParams) => { + try { + const result = await prisma.orgService.findUniqueOrThrow({ + where: input, + select: { + serviceName: globalSelect.freeText(), + services: { + where: { + tag: { + active: true, + }, + }, + select: { + tag: { + select: { + defaultAttributes: { + select: { + attribute: { + select: { + tsKey: true, + tsNs: true, + }, + }, + }, + }, + category: { + select: { + tsKey: true, + tsNs: true, + }, + }, + tsKey: true, + tsNs: true, + active: true, + }, + }, + }, + }, + serviceAreas: { + select: { + countries: { + select: { + country: globalSelect.country(), + }, + }, + districts: { + select: { + govDist: globalSelect.govDistExpanded(), + }, + }, + }, + }, + hours: { + select: { + dayIndex: true, + start: true, + end: true, + closed: true, + tz: true, + }, + }, + reviews: { + where: { + visible: true, + deleted: false, + }, + select: { + language: globalSelect.language(), + lcrCountry: globalSelect.country(), + lcrGovDist: globalSelect.govDistExpanded(), + translatedText: { + select: { + text: true, + language: { + select: { + localeCode: true, + }, + }, + }, + }, + }, + }, + attributes: { + where: { attribute: { active: true, categories: { some: { category: { active: true } } } } }, + select: select.attributes(), + }, + phones: { + where: { active: true, phone: globalWhere.isPublic() }, + select: { phone: globalSelect.orgPhone() }, + }, + emails: { + where: { + active: true, + email: globalWhere.isPublic(), + }, + select: { + email: { + include: { + title: { + select: { + tsKey: true, + tsNs: true, + }, + }, + }, + }, + }, + }, + accessDetails: { + where: { + active: true, + }, + select: select.attributes(), + }, + locations: { + where: { + active: true, + location: globalWhere.isPublic(), + }, + select: { + location: { + select: { + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + primary: true, + govDist: globalSelect.govDistBasic(), + country: globalSelect.country(), + longitude: true, + latitude: true, + }, + }, + }, + }, + id: true, + description: globalSelect.freeText(), + }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/query.byId.schema.ts b/packages/api/router/service/query.byId.schema.ts new file mode 100644 index 0000000000..811be1316a --- /dev/null +++ b/packages/api/router/service/query.byId.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZByIdSchema = z.object({ id: prefixedId('orgService') }) +export type TByIdSchema = z.infer diff --git a/packages/api/router/service/query.byOrgId.handler.ts b/packages/api/router/service/query.byOrgId.handler.ts new file mode 100644 index 0000000000..b111ca0bb7 --- /dev/null +++ b/packages/api/router/service/query.byOrgId.handler.ts @@ -0,0 +1,51 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalSelect, globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TByOrgIdSchema } from './query.byOrgId.schema' +import { select } from './selects' + +export const byOrgId = async ({ input }: TRPCHandlerParams) => { + try { + const results = await prisma.orgService.findMany({ + where: input, + select: { + id: true, + attributes: { + where: { active: true, attribute: globalWhere.attributesByTag(['offers-remote-services']) }, + select: select.attributes(), + }, + serviceName: globalSelect.freeText(), + + services: { + where: { tag: { active: true } }, + select: { + tag: { + select: { + category: { select: { tsKey: true, tsNs: true } }, + tsKey: true, + tsNs: true, + active: true, + }, + }, + }, + }, + + locations: { + where: { location: globalWhere.isPublic() }, + select: { + location: { + select: { + name: true, + }, + }, + }, + }, + }, + }) + return results + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/query.byOrgId.schema.ts b/packages/api/router/service/query.byOrgId.schema.ts new file mode 100644 index 0000000000..2a0bc820bb --- /dev/null +++ b/packages/api/router/service/query.byOrgId.schema.ts @@ -0,0 +1,8 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZByOrgIdSchema = z.object({ + organizationId: prefixedId('organization'), +}) +export type TByOrgIdSchema = z.infer diff --git a/packages/api/router/service/query.byOrgLocationId.handler.ts b/packages/api/router/service/query.byOrgLocationId.handler.ts new file mode 100644 index 0000000000..f666dbfe01 --- /dev/null +++ b/packages/api/router/service/query.byOrgLocationId.handler.ts @@ -0,0 +1,153 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalSelect, globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TByOrgLocationIdSchema } from './query.byOrgLocationId.schema' +import { select } from './selects' + +export const byOrgLocationId = async ({ input }: TRPCHandlerParams) => { + try { + const results = await prisma.orgService.findMany({ + where: { locations: { some: input } }, + select: { + serviceName: globalSelect.freeText(), + services: { + where: { + tag: { + active: true, + }, + }, + select: { + tag: { + select: { + defaultAttributes: { + select: { + attribute: { + select: { + tsKey: true, + tsNs: true, + }, + }, + }, + }, + category: { + select: { + tsKey: true, + tsNs: true, + }, + }, + tsKey: true, + tsNs: true, + active: true, + }, + }, + }, + }, + serviceAreas: { + select: { + countries: { + select: { + country: globalSelect.country(), + }, + }, + districts: { + select: { + govDist: globalSelect.govDistExpanded(), + }, + }, + }, + }, + hours: { + select: { + dayIndex: true, + start: true, + end: true, + closed: true, + tz: true, + }, + }, + reviews: { + where: { + visible: true, + deleted: false, + }, + select: { + language: globalSelect.language(), + lcrCountry: globalSelect.country(), + lcrGovDist: globalSelect.govDistExpanded(), + translatedText: { + select: { + text: true, + language: { + select: { + localeCode: true, + }, + }, + }, + }, + }, + }, + attributes: { + where: { attribute: { active: true, categories: { some: { category: { active: true } } } } }, + select: select.attributes(), + }, + phones: { + where: { active: true, phone: globalWhere.isPublic() }, + select: { phone: globalSelect.orgPhone() }, + }, + emails: { + where: { + active: true, + email: globalWhere.isPublic(), + }, + select: { + email: { + include: { + title: { + select: { + tsKey: true, + tsNs: true, + }, + }, + }, + }, + }, + }, + accessDetails: { + where: { + active: true, + }, + select: select.attributes(), + }, + locations: { + where: { + active: true, + location: globalWhere.isPublic(), + }, + select: { + location: { + select: { + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + primary: true, + govDist: globalSelect.govDistBasic(), + country: globalSelect.country(), + longitude: true, + latitude: true, + }, + }, + }, + }, + id: true, + description: globalSelect.freeText(), + }, + }) + return results + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/query.byOrgLocationId.schema.ts b/packages/api/router/service/query.byOrgLocationId.schema.ts new file mode 100644 index 0000000000..53a35b2cde --- /dev/null +++ b/packages/api/router/service/query.byOrgLocationId.schema.ts @@ -0,0 +1,8 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZByOrgLocationIdSchema = z.object({ + orgLocationId: prefixedId('orgLocation'), +}) +export type TByOrgLocationIdSchema = z.infer diff --git a/packages/api/router/service/query.byUserListId.handler.ts b/packages/api/router/service/query.byUserListId.handler.ts new file mode 100644 index 0000000000..d6622166e8 --- /dev/null +++ b/packages/api/router/service/query.byUserListId.handler.ts @@ -0,0 +1,156 @@ +import { TRPCError } from '@trpc/server' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalSelect, globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TByUserListIdSchema } from './query.byUserListId.schema' +import { select } from './selects' + +export const byUserListId = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const results = await prisma.orgService.findMany({ + where: { userLists: { some: { list: { AND: { id: input.listId, ownedById: ctx.session.user.id } } } } }, + select: { + serviceName: globalSelect.freeText(), + services: { + where: { + tag: { + active: true, + }, + }, + select: { + tag: { + select: { + defaultAttributes: { + select: { + attribute: { + select: { + tsKey: true, + tsNs: true, + }, + }, + }, + }, + category: { + select: { + tsKey: true, + tsNs: true, + }, + }, + tsKey: true, + tsNs: true, + active: true, + }, + }, + }, + }, + serviceAreas: { + select: { + countries: { + select: { + country: globalSelect.country(), + }, + }, + districts: { + select: { + govDist: globalSelect.govDistExpanded(), + }, + }, + }, + }, + hours: { + select: { + dayIndex: true, + start: true, + end: true, + closed: true, + tz: true, + }, + }, + reviews: { + where: { + visible: true, + deleted: false, + }, + select: { + language: globalSelect.language(), + lcrCountry: globalSelect.country(), + lcrGovDist: globalSelect.govDistExpanded(), + translatedText: { + select: { + text: true, + language: { + select: { + localeCode: true, + }, + }, + }, + }, + }, + }, + attributes: { + where: { attribute: { active: true, categories: { some: { category: { active: true } } } } }, + select: select.attributes(), + }, + phones: { + where: { active: true, phone: globalWhere.isPublic() }, + select: { phone: globalSelect.orgPhone() }, + }, + emails: { + where: { + active: true, + email: globalWhere.isPublic(), + }, + select: { + email: { + include: { + title: { + select: { + tsKey: true, + tsNs: true, + }, + }, + }, + }, + }, + }, + accessDetails: { + where: { + active: true, + }, + select: select.attributes(), + }, + locations: { + where: { + active: true, + location: globalWhere.isPublic(), + }, + select: { + location: { + select: { + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + primary: true, + govDist: globalSelect.govDistBasic(), + country: globalSelect.country(), + longitude: true, + latitude: true, + }, + }, + }, + }, + id: true, + description: globalSelect.freeText(), + }, + }) + + return results + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/query.byUserListId.schema.ts b/packages/api/router/service/query.byUserListId.schema.ts new file mode 100644 index 0000000000..1301eaca69 --- /dev/null +++ b/packages/api/router/service/query.byUserListId.schema.ts @@ -0,0 +1,8 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZByUserListIdSchema = z.object({ + listId: prefixedId('userSavedList'), +}) +export type TByUserListIdSchema = z.infer diff --git a/packages/api/router/service/query.forServiceDrawer.handler.ts b/packages/api/router/service/query.forServiceDrawer.handler.ts new file mode 100644 index 0000000000..b205ab94a1 --- /dev/null +++ b/packages/api/router/service/query.forServiceDrawer.handler.ts @@ -0,0 +1,95 @@ +import mapObjectVals from 'just-map-values' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { transformer } from '~api/lib/transformer' +import { globalSelect, globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForServiceDrawerSchema } from './query.forServiceDrawer.schema' +import { select } from './selects' + +export const forServiceDrawer = async ({ input }: TRPCHandlerParams) => { + try { + const results = await prisma.orgService.findMany({ + where: input, + select: { + id: true, + attributes: { + where: { active: true, attribute: globalWhere.attributesByTag(['offers-remote-services']) }, + select: select.attributes(), + }, + serviceName: globalSelect.freeText(), + + services: { + where: { tag: { active: true } }, + select: { + tag: { + select: { + category: { select: { tsKey: true, tsNs: true, id: true } }, + tsKey: true, + tsNs: true, + active: true, + }, + }, + }, + }, + + locations: { + where: { location: globalWhere.isPublic() }, + select: { + location: { + select: { + name: true, + }, + }, + }, + }, + }, + }) + + let servObj: ServObj = {} + for (const service of results) { + servObj = service.services.reduce((items: ServObj, record) => { + const key = record.tag.category.tsKey + if (!items[key]) { + items[key] = new Set() + } + const itemToAdd = { + id: service.id, + name: { + tsNs: service.serviceName?.ns, + tsKey: service.serviceName?.key, + defaultText: service.serviceName?.tsKey.text, + }, + locations: service.locations.map(({ location }) => location.name), + attributes: service.attributes.map(({ attribute }) => { + const { id, tsKey, tsNs } = attribute + return { id, tsKey, tsNs } + }), + } satisfies ServItem + + items[key]?.add(transformer.stringify(itemToAdd)) + return items + }, servObj) + } + const transformed = mapObjectVals(servObj, (value) => + [...value].map((item) => transformer.parse(item)) + ) + return transformed + } catch (error) { + handleError(error) + } +} + +type ServObj = { [k: string]: Set } +type ServItem = { + id: string + name: { + tsNs?: string + tsKey?: string + defaultText?: string + } + locations: (string | null)[] + attributes: { id: string; tsKey: string; tsNs: string }[] +} diff --git a/packages/api/router/service/query.forServiceDrawer.schema.ts b/packages/api/router/service/query.forServiceDrawer.schema.ts new file mode 100644 index 0000000000..8f45a25b97 --- /dev/null +++ b/packages/api/router/service/query.forServiceDrawer.schema.ts @@ -0,0 +1,8 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForServiceDrawerSchema = z.object({ + organizationId: prefixedId('organization'), +}) +export type TForServiceDrawerSchema = z.infer diff --git a/packages/api/router/service/query.forServiceEditDrawer.handler.ts b/packages/api/router/service/query.forServiceEditDrawer.handler.ts new file mode 100644 index 0000000000..3998af2d89 --- /dev/null +++ b/packages/api/router/service/query.forServiceEditDrawer.handler.ts @@ -0,0 +1,107 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { transformer } from '~api/lib/transformer' +import { globalSelect } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForServiceEditDrawerSchema } from './query.forServiceEditDrawer.schema' + +export const forServiceEditDrawer = async ({ input }: TRPCHandlerParams) => { + try { + const result = await prisma.orgService.findUniqueOrThrow({ + where: { id: input }, + select: { + id: true, + accessDetails: { + select: { + attribute: { select: { id: true, tsKey: true, tsNs: true } }, + supplement: { + select: { id: true, text: globalSelect.freeText({ withCrowdinId: true }), data: true }, + }, + }, + }, + attributes: { + select: { + attribute: { + select: { + id: true, + tsKey: true, + tsNs: true, + icon: true, + categories: { select: { category: { select: { tag: true } } } }, + }, + }, + supplement: { + select: { + id: true, + active: true, + data: true, + boolean: true, + countryId: true, + govDistId: true, + languageId: true, + text: globalSelect.freeText({ withCrowdinId: true }), + }, + }, + }, + }, + description: globalSelect.freeText({ withCrowdinId: true }), + phones: { select: { phone: { select: { id: true } } } }, + emails: { select: { email: { select: { id: true } } } }, + locations: { select: { location: { select: { id: true } } } }, + hours: { select: { id: true, dayIndex: true, start: true, end: true, closed: true, tz: true } }, + services: { select: { tag: { select: { id: true, categoryId: true } } } }, + serviceAreas: { + select: { + id: true, + countries: { select: { country: { select: { id: true } } } }, + districts: { select: { govDist: { select: { id: true } } } }, + }, + }, + published: true, + deleted: true, + serviceName: globalSelect.freeText({ withCrowdinId: true }), + }, + }) + const { attributes, phones, emails, locations, services, serviceAreas, accessDetails, ...rest } = result + const transformed = { + ...rest, + phones: phones.map(({ phone }) => phone.id), + emails: emails.map(({ email }) => email.id), + locations: locations.map(({ location }) => location.id), + services: services.map(({ tag }) => ({ id: tag.id, categoryId: tag.categoryId })), + serviceAreas: serviceAreas + ? { + id: serviceAreas.id, + countries: serviceAreas.countries.map(({ country }) => country.id), + districts: serviceAreas.districts.map(({ govDist }) => govDist.id), + } + : null, + attributes: attributes.map(({ attribute, supplement }) => { + const { categories, ...attr } = attribute + return { + attribute: { ...attr, categories: categories.map(({ category }) => category.tag) }, + supplement: supplement.map(({ data, ...rest }) => { + if (data) { + return { ...rest, data: transformer.parse(JSON.stringify(data)) } + } + return { ...rest, data } + }), + } + }), + accessDetails: accessDetails.map(({ attribute, supplement }) => ({ + attribute, + supplement: supplement.map(({ data, ...rest }) => { + if (data) { + return { ...rest, data: transformer.parse(JSON.stringify(data)) } + } + return { ...rest, data } + }), + })), + } + + return transformed + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/query.forServiceEditDrawer.schema.ts b/packages/api/router/service/query.forServiceEditDrawer.schema.ts new file mode 100644 index 0000000000..a639404359 --- /dev/null +++ b/packages/api/router/service/query.forServiceEditDrawer.schema.ts @@ -0,0 +1,6 @@ +import { type z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForServiceEditDrawerSchema = prefixedId('orgService') +export type TForServiceEditDrawerSchema = z.infer diff --git a/packages/api/router/service/query.forServiceInfoCard.handler.ts b/packages/api/router/service/query.forServiceInfoCard.handler.ts new file mode 100644 index 0000000000..055f6c4adf --- /dev/null +++ b/packages/api/router/service/query.forServiceInfoCard.handler.ts @@ -0,0 +1,51 @@ +import { isIdFor, prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForServiceInfoCardSchema } from './query.forServiceInfoCard.schema' + +export const forServiceInfoCard = async ({ input }: TRPCHandlerParams) => { + try { + const result = await prisma.orgService.findMany({ + where: { + ...globalWhere.isPublic(), + ...(isIdFor('organization', input.parentId) + ? { organization: { id: input.parentId, ...globalWhere.isPublic() } } + : { locations: { some: { location: { id: input.parentId, ...globalWhere.isPublic() } } } }), + ...(input.remoteOnly + ? { attributes: { some: { attribute: { active: true, tag: 'offers-remote-services' } } } } + : {}), + OR: [{ crisisSupportOnly: null }, { crisisSupportOnly: false }], + }, + select: { + id: true, + serviceName: { select: { key: true, ns: true, tsKey: { select: { text: true } } } }, + services: { + select: { + tag: { + select: { tsKey: true, category: { select: { tsKey: true } } }, + }, + }, + where: { tag: { active: true, category: { active: true } } }, + }, + attributes: { + where: { attribute: { active: true, tag: 'offers-remote-services' } }, + select: { attributeId: true }, + }, + }, + }) + + const transformed = result.map(({ id, serviceName, services, attributes }) => ({ + id, + serviceName: serviceName + ? { tsKey: serviceName.tsKey, tsNs: serviceName.ns, defaultText: serviceName.tsKey.text } + : null, + serviceCategories: [...new Set(services.map(({ tag }) => tag.category.tsKey))].sort(), + offersRemote: attributes.length > 0, + })) + return transformed + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/query.forServiceInfoCard.schema.ts b/packages/api/router/service/query.forServiceInfoCard.schema.ts new file mode 100644 index 0000000000..09f03d7ffb --- /dev/null +++ b/packages/api/router/service/query.forServiceInfoCard.schema.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForServiceInfoCardSchema = z.object({ + parentId: prefixedId('organization').or(prefixedId('orgLocation')), + remoteOnly: z.boolean().optional(), +}) +export type TForServiceInfoCardSchema = z.infer diff --git a/packages/api/router/service/query.forServiceModal.handler.ts b/packages/api/router/service/query.forServiceModal.handler.ts new file mode 100644 index 0000000000..212c748502 --- /dev/null +++ b/packages/api/router/service/query.forServiceModal.handler.ts @@ -0,0 +1,65 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForServiceModalSchema } from './query.forServiceModal.schema' +import { select } from './selects' + +export const forServiceModal = async ({ input }: TRPCHandlerParams) => { + try { + const result = await prisma.orgService.findUniqueOrThrow({ + where: { id: input, ...globalWhere.isPublic() }, + select: { + id: true, + services: { select: { tag: { select: { tsKey: true } } }, where: { tag: { active: true } } }, + accessDetails: { + where: { active: true }, + + select: { + attribute: { select: { id: true } }, + supplement: { + where: { active: true }, + select: { + id: true, + data: true, + text: { select: { key: true, tsKey: { select: { text: true } } } }, + }, + }, + }, + }, + serviceName: { + select: { + key: true, + ns: true, + tsKey: { + select: { + text: true, + }, + }, + }, + }, + locations: { + where: { location: globalWhere.isPublic() }, + select: { location: { select: { country: { select: { cca2: true } } } } }, + }, + attributes: { select: select.attributes() }, + hours: { where: { active: true }, select: { _count: true } }, + description: { + select: { + key: true, + ns: true, + tsKey: { + select: { + text: true, + }, + }, + }, + }, + }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/query.forServiceModal.schema.ts b/packages/api/router/service/query.forServiceModal.schema.ts new file mode 100644 index 0000000000..a63f20d42a --- /dev/null +++ b/packages/api/router/service/query.forServiceModal.schema.ts @@ -0,0 +1,6 @@ +import { type z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForServiceModalSchema = prefixedId('orgService') +export type TForServiceModalSchema = z.infer diff --git a/packages/api/router/service/query.getFilterOptions.handler.ts b/packages/api/router/service/query.getFilterOptions.handler.ts new file mode 100644 index 0000000000..3dd8e72ab9 --- /dev/null +++ b/packages/api/router/service/query.getFilterOptions.handler.ts @@ -0,0 +1,37 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' + +export const getFilterOptions = async () => { + try { + const result = await prisma.serviceCategory.findMany({ + where: { + active: true, + OR: [{ crisisSupportOnly: null }, { crisisSupportOnly: false }], + }, + select: { + id: true, + tsKey: true, + tsNs: true, + services: { + where: { + active: true, + }, + select: { + id: true, + tsKey: true, + tsNs: true, + }, + orderBy: { + name: 'asc', + }, + }, + }, + orderBy: { + category: 'asc', + }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/query.getNames.handler.ts b/packages/api/router/service/query.getNames.handler.ts new file mode 100644 index 0000000000..5c35ee4719 --- /dev/null +++ b/packages/api/router/service/query.getNames.handler.ts @@ -0,0 +1,42 @@ +import { TRPCError } from '@trpc/server' +import flush from 'just-flush' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetNamesSchema } from './query.getNames.schema' + +export const getNames = async ({ input }: TRPCHandlerParams) => { + try { + const { orgLocationId, organizationId } = input + + if (!orgLocationId && !organizationId) throw new TRPCError({ code: 'BAD_REQUEST' }) + + const results = await prisma.orgService.findMany({ + where: { + organizationId: organizationId, + ...(orgLocationId + ? { + locations: { + some: { orgLocationId }, + }, + } + : {}), + }, + select: { + id: true, + serviceName: { select: { key: true, tsKey: { select: { text: true } } } }, + }, + }) + const transformedResults = flush( + results.map(({ id, serviceName }) => { + if (!serviceName) return + return { id, tsKey: serviceName.key, defaultText: serviceName.tsKey.text } + }) + ) + return transformedResults + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/query.getNames.schema.ts b/packages/api/router/service/query.getNames.schema.ts new file mode 100644 index 0000000000..93bdc56afd --- /dev/null +++ b/packages/api/router/service/query.getNames.schema.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetNamesSchema = z.union([ + z.object({ organizationId: prefixedId('organization'), orgLocationId: z.never() }), + z.object({ organizationId: z.never(), orgLocationId: prefixedId('orgLocation') }), +]) +export type TGetNamesSchema = z.infer diff --git a/packages/api/router/service/query.getOptions.handler.ts b/packages/api/router/service/query.getOptions.handler.ts new file mode 100644 index 0000000000..da9a01288e --- /dev/null +++ b/packages/api/router/service/query.getOptions.handler.ts @@ -0,0 +1,26 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' + +export const getOptions = async () => { + try { + const result = await prisma.serviceTag.findMany({ + select: { + id: true, + active: true, + tsKey: true, + tsNs: true, + category: { + select: { + id: true, + active: true, + tsKey: true, + tsNs: true, + }, + }, + }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/query.getParentName.handler.ts b/packages/api/router/service/query.getParentName.handler.ts new file mode 100644 index 0000000000..35c4095c11 --- /dev/null +++ b/packages/api/router/service/query.getParentName.handler.ts @@ -0,0 +1,33 @@ +import { TRPCError } from '@trpc/server' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetParentNameSchema } from './query.getParentName.schema' + +export const getParentName = async ({ input }: TRPCHandlerParams) => { + try { + const { slug, orgLocationId } = input + + switch (true) { + case Boolean(slug): { + return prisma.organization.findUniqueOrThrow({ + where: { slug }, + select: { name: true }, + }) + } + case Boolean(orgLocationId): { + return prisma.orgLocation.findUniqueOrThrow({ + where: { id: orgLocationId }, + select: { name: true }, + }) + } + default: { + throw new TRPCError({ code: 'BAD_REQUEST' }) + } + } + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/service/query.getParentName.schema.ts b/packages/api/router/service/query.getParentName.schema.ts new file mode 100644 index 0000000000..2214a19326 --- /dev/null +++ b/packages/api/router/service/query.getParentName.schema.ts @@ -0,0 +1,8 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetParentNameSchema = z + .object({ slug: z.string(), orgLocationId: z.never() }) + .or(z.object({ orgLocationId: prefixedId('orgLocation'), slug: z.never() })) +export type TGetParentNameSchema = z.infer diff --git a/packages/api/router/service/schemas.ts b/packages/api/router/service/schemas.ts new file mode 100644 index 0000000000..9e78129289 --- /dev/null +++ b/packages/api/router/service/schemas.ts @@ -0,0 +1,20 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.attachServiceAttribute.schema' +export * from './mutation.attachServiceTags.schema' +export * from './mutation.create.schema' +export * from './mutation.createAccessInstructions.schema' +export * from './mutation.createServiceArea.schema' +export * from './mutation.linkEmails.schema' +export * from './mutation.linkPhones.schema' +export * from './mutation.update.schema' +export * from './query.byId.schema' +export * from './query.byOrgId.schema' +export * from './query.byOrgLocationId.schema' +export * from './query.byUserListId.schema' +export * from './query.forServiceDrawer.schema' +export * from './query.forServiceEditDrawer.schema' +export * from './query.forServiceInfoCard.schema' +export * from './query.forServiceModal.schema' +export * from './query.getNames.schema' +export * from './query.getParentName.schema' +// codegen:end diff --git a/packages/api/router/service/selects.ts b/packages/api/router/service/selects.ts new file mode 100644 index 0000000000..0d3ec82fda --- /dev/null +++ b/packages/api/router/service/selects.ts @@ -0,0 +1,51 @@ +import { type Prisma } from '@weareinreach/db' +import { globalSelect } from '~api/selects/global' + +export const select = { + attributes() { + return { + attribute: { + select: { + id: true, + tsKey: true, + tsNs: true, + icon: true, + iconBg: true, + showOnLocation: true, + categories: { + select: { + category: { + select: { + tag: true, + icon: true, + }, + }, + }, + }, + _count: { + select: { + parents: true, + children: true, + }, + }, + }, + }, + supplement: { + select: { + id: true, + country: globalSelect.country(), + language: { + select: { + languageName: true, + nativeName: true, + }, + }, + text: globalSelect.freeText(), + govDist: globalSelect.govDistExpanded(), + boolean: true, + data: true, + }, + }, + } satisfies Prisma.ServiceAttributeSelect + }, +} From 48870640e325a3144be726f590eb03f3fbacf048 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:37:57 -0400 Subject: [PATCH 17/63] create one or many base --- packages/api/schemaBase/createOneOrMany.ts | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 packages/api/schemaBase/createOneOrMany.ts diff --git a/packages/api/schemaBase/createOneOrMany.ts b/packages/api/schemaBase/createOneOrMany.ts new file mode 100644 index 0000000000..921a402f04 --- /dev/null +++ b/packages/api/schemaBase/createOneOrMany.ts @@ -0,0 +1,23 @@ +import { z } from 'zod' + +/** + * Base to create one or many records. Pass the schema in as a singular object (this fuction will + * automatically union it with an array of the object) + */ +export const CreateOneOrManyBase = < + TSchema extends z.ZodObject< + z.ZodRawShape, + 'strip', + z.ZodTypeAny, + z.objectOutputType + >, +>( + schema: TSchema +) => ({ + dataParser: z.object({ + actorId: z.string(), + data: schema.or(schema.array()), + operation: z.enum(['CREATE', 'LINK', 'UNLINK']), + }), + inputSchema: schema.or(schema.array()), +}) From b5c66d733e0c4167b451d61a9f0e8e33b5f2fe2a Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:38:24 -0400 Subject: [PATCH 18/63] update types to narrow context --- packages/api/types/handler.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/api/types/handler.ts b/packages/api/types/handler.ts index 729cbb43c2..5434c7c311 100644 --- a/packages/api/types/handler.ts +++ b/packages/api/types/handler.ts @@ -1,5 +1,10 @@ +import { type SetNonNullable } from 'type-fest' + import { type Context } from '~api/lib/context' -export type TRPCHandlerParams = { - ctx: Context +type ContextScenario = 'public' | 'protected' +type AuthedContext = SetNonNullable & { actorId: string } + +export type TRPCHandlerParams = { + ctx: TScenario extends 'public' ? Context : AuthedContext } & (undefined extends TInput ? { input?: never } : { input: TInput }) From efd538f6cc644eac10eda2e3b46b8ccabd011562 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:39:13 -0400 Subject: [PATCH 19/63] add crowdinId option, convert types to satisfies --- packages/api/selects/global.ts | 76 ++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/packages/api/selects/global.ts b/packages/api/selects/global.ts index ebe67b3ac7..bd447027d1 100644 --- a/packages/api/selects/global.ts +++ b/packages/api/selects/global.ts @@ -1,7 +1,7 @@ import { type Prisma } from '@weareinreach/db' export const globalSelect = { - freeText(): Prisma.FreeTextDefaultArgs { + freeText(opts?: FreeTextOpts) { return { select: { key: true, @@ -9,12 +9,13 @@ export const globalSelect = { tsKey: { select: { text: true, + crowdinId: opts?.withCrowdinId, }, }, }, - } + } satisfies Prisma.FreeTextDefaultArgs }, - country(): Prisma.CountryDefaultArgs { + country() { return { select: { cca2: true, @@ -26,9 +27,9 @@ export const globalSelect = { tsKey: true, tsNs: true, }, - } + } satisfies Prisma.CountryDefaultArgs }, - govDistBasic(): Prisma.GovDistDefaultArgs { + govDistBasic() { return { select: { govDistType: { @@ -41,9 +42,9 @@ export const globalSelect = { tsNs: true, abbrev: true, }, - } + } satisfies Prisma.GovDistDefaultArgs }, - govDistExpanded(): Prisma.GovDistDefaultArgs { + govDistExpanded() { return { select: { id: true, @@ -81,17 +82,17 @@ export const globalSelect = { }, }, }, - } + } satisfies Prisma.GovDistDefaultArgs }, - language(): Prisma.LanguageDefaultArgs { + language() { return { select: { languageName: true, nativeName: true, }, - } + } satisfies Prisma.LanguageDefaultArgs }, - orgWebsite(): Prisma.OrgWebsiteDefaultArgs { + orgWebsite() { return { select: { id: true, @@ -102,18 +103,18 @@ export const globalSelect = { orgLocationId: true, orgLocationOnly: true, }, - } + } satisfies Prisma.OrgWebsiteDefaultArgs }, - orgPhoto(): Prisma.OrgPhotoDefaultArgs { + orgPhoto() { return { select: { src: true, height: true, width: true, }, - } + } satisfies Prisma.OrgPhotoDefaultArgs }, - hours(): Prisma.OrgHoursDefaultArgs { + hours() { return { select: { dayIndex: true, @@ -122,9 +123,9 @@ export const globalSelect = { closed: true, tz: true, }, - } + } satisfies Prisma.OrgHoursDefaultArgs }, - serviceTags(): Prisma.ServiceTagDefaultArgs { + serviceTags() { return { select: { name: true, @@ -146,17 +147,17 @@ export const globalSelect = { }, }, }, - } + } satisfies Prisma.ServiceTagDefaultArgs }, - serviceArea(): Prisma.ServiceAreaDefaultArgs { + serviceArea() { return { select: { countries: { select: { country: this.country() } }, districts: { select: { govDist: this.govDistBasic() } }, }, - } + } satisfies Prisma.ServiceAreaDefaultArgs }, - socialMedia(): Prisma.OrgSocialMediaDefaultArgs { + socialMedia() { return { select: { service: { @@ -171,7 +172,21 @@ export const globalSelect = { url: true, username: true, }, - } + } satisfies Prisma.OrgSocialMediaDefaultArgs + }, + orgPhone() { + return { + select: { + country: this.country(), + phoneLangs: { select: { language: this.language() } }, + phoneType: { select: { tsKey: true, tsNs: true } }, + description: this.freeText(), + number: true, + ext: true, + primary: true, + locationOnly: true, + }, + } satisfies Prisma.OrgPhoneDefaultArgs }, } @@ -182,4 +197,21 @@ export const globalWhere = { deleted: false, } as const }, + attributesByTag(tags: string[]): Prisma.AttributeWhereInput { + return { + active: true, + tag: { in: tags }, + categories: { + some: { + category: { + active: true, + }, + }, + }, + } + }, +} + +interface FreeTextOpts { + withCrowdinId?: boolean } From 669baa4b577182f504afa12891f8c7f3bd58a86a Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:46:18 -0400 Subject: [PATCH 20/63] create router dirs --- packages/api/router/{fieldOpt.ts => fieldOpt/index.ts} | 0 packages/api/router/{geo.ts => geo/index.ts} | 0 packages/api/router/{internalNote.ts => internalNote/index.ts} | 0 packages/api/router/{misc.ts => misc/index.ts} | 0 packages/api/router/{orgEmail.ts => orgEmail/index.ts} | 0 packages/api/router/{orgHours.ts => orgHours/index.ts} | 0 packages/api/router/{orgPhone.ts => orgPhone/index.ts} | 0 packages/api/router/{orgPhoto.ts => orgPhoto/index.ts} | 0 .../api/router/{orgSocialMedia.ts => orgSocialMedia/index.ts} | 0 packages/api/router/{orgWebsite.ts => orgWebsite/index.ts} | 0 packages/api/router/{quicklink.ts => quicklink/index.ts} | 0 packages/api/router/{review.ts => review/index.ts} | 0 packages/api/router/{savedLists.ts => savedLists/index.ts} | 0 packages/api/router/{user.ts => user/index.ts} | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename packages/api/router/{fieldOpt.ts => fieldOpt/index.ts} (100%) rename packages/api/router/{geo.ts => geo/index.ts} (100%) rename packages/api/router/{internalNote.ts => internalNote/index.ts} (100%) rename packages/api/router/{misc.ts => misc/index.ts} (100%) rename packages/api/router/{orgEmail.ts => orgEmail/index.ts} (100%) rename packages/api/router/{orgHours.ts => orgHours/index.ts} (100%) rename packages/api/router/{orgPhone.ts => orgPhone/index.ts} (100%) rename packages/api/router/{orgPhoto.ts => orgPhoto/index.ts} (100%) rename packages/api/router/{orgSocialMedia.ts => orgSocialMedia/index.ts} (100%) rename packages/api/router/{orgWebsite.ts => orgWebsite/index.ts} (100%) rename packages/api/router/{quicklink.ts => quicklink/index.ts} (100%) rename packages/api/router/{review.ts => review/index.ts} (100%) rename packages/api/router/{savedLists.ts => savedLists/index.ts} (100%) rename packages/api/router/{user.ts => user/index.ts} (100%) diff --git a/packages/api/router/fieldOpt.ts b/packages/api/router/fieldOpt/index.ts similarity index 100% rename from packages/api/router/fieldOpt.ts rename to packages/api/router/fieldOpt/index.ts diff --git a/packages/api/router/geo.ts b/packages/api/router/geo/index.ts similarity index 100% rename from packages/api/router/geo.ts rename to packages/api/router/geo/index.ts diff --git a/packages/api/router/internalNote.ts b/packages/api/router/internalNote/index.ts similarity index 100% rename from packages/api/router/internalNote.ts rename to packages/api/router/internalNote/index.ts diff --git a/packages/api/router/misc.ts b/packages/api/router/misc/index.ts similarity index 100% rename from packages/api/router/misc.ts rename to packages/api/router/misc/index.ts diff --git a/packages/api/router/orgEmail.ts b/packages/api/router/orgEmail/index.ts similarity index 100% rename from packages/api/router/orgEmail.ts rename to packages/api/router/orgEmail/index.ts diff --git a/packages/api/router/orgHours.ts b/packages/api/router/orgHours/index.ts similarity index 100% rename from packages/api/router/orgHours.ts rename to packages/api/router/orgHours/index.ts diff --git a/packages/api/router/orgPhone.ts b/packages/api/router/orgPhone/index.ts similarity index 100% rename from packages/api/router/orgPhone.ts rename to packages/api/router/orgPhone/index.ts diff --git a/packages/api/router/orgPhoto.ts b/packages/api/router/orgPhoto/index.ts similarity index 100% rename from packages/api/router/orgPhoto.ts rename to packages/api/router/orgPhoto/index.ts diff --git a/packages/api/router/orgSocialMedia.ts b/packages/api/router/orgSocialMedia/index.ts similarity index 100% rename from packages/api/router/orgSocialMedia.ts rename to packages/api/router/orgSocialMedia/index.ts diff --git a/packages/api/router/orgWebsite.ts b/packages/api/router/orgWebsite/index.ts similarity index 100% rename from packages/api/router/orgWebsite.ts rename to packages/api/router/orgWebsite/index.ts diff --git a/packages/api/router/quicklink.ts b/packages/api/router/quicklink/index.ts similarity index 100% rename from packages/api/router/quicklink.ts rename to packages/api/router/quicklink/index.ts diff --git a/packages/api/router/review.ts b/packages/api/router/review/index.ts similarity index 100% rename from packages/api/router/review.ts rename to packages/api/router/review/index.ts diff --git a/packages/api/router/savedLists.ts b/packages/api/router/savedLists/index.ts similarity index 100% rename from packages/api/router/savedLists.ts rename to packages/api/router/savedLists/index.ts diff --git a/packages/api/router/user.ts b/packages/api/router/user/index.ts similarity index 100% rename from packages/api/router/user.ts rename to packages/api/router/user/index.ts From 9fa4e30e51185367e519b416908079a380786557 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:26:36 -0400 Subject: [PATCH 21/63] account for undefined union type --- packages/api/types/handler.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/api/types/handler.ts b/packages/api/types/handler.ts index 5434c7c311..c3df737e08 100644 --- a/packages/api/types/handler.ts +++ b/packages/api/types/handler.ts @@ -7,4 +7,8 @@ type AuthedContext = SetNonNullable & { actorId: string } export type TRPCHandlerParams = { ctx: TScenario extends 'public' ? Context : AuthedContext -} & (undefined extends TInput ? { input?: never } : { input: TInput }) +} & (undefined extends TInput + ? [TInput] extends [undefined] + ? { input?: never } + : { input: TInput } + : { input: TInput }) From 15da01ac3a14cf12ea340e13d0acf2998fbb2bd8 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:27:09 -0400 Subject: [PATCH 22/63] fieldOpts router caching --- packages/api/router/fieldOpt/index.ts | 254 +++++------------- .../query.attributeCategories.handler.ts | 24 ++ .../query.attributeCategories.schema.ts | 4 + .../query.attributesByCategory.handler.ts | 34 +++ .../query.attributesByCategory.schema.ts | 8 + .../fieldOpt/query.countries.handler.ts | 31 +++ .../router/fieldOpt/query.countries.schema.ts | 10 + .../query.countryGovDistMap.handler.ts | 53 ++++ .../query.govDistsByCountry.handler.ts | 65 +++++ .../query.govDistsByCountry.schema.ts | 10 + .../query.govDistsByCountryNoSub.handler.ts | 43 +++ .../query.govDistsByCountryNoSub.schema.ts | 10 + .../fieldOpt/query.languages.handler.ts | 25 ++ .../router/fieldOpt/query.languages.schema.ts | 7 + .../fieldOpt/query.phoneTypes.handler.ts | 19 ++ .../fieldOpt/query.userTitle.handler.ts | 14 + packages/api/router/fieldOpt/schemas.ts | 8 + 17 files changed, 439 insertions(+), 180 deletions(-) create mode 100644 packages/api/router/fieldOpt/query.attributeCategories.handler.ts create mode 100644 packages/api/router/fieldOpt/query.attributeCategories.schema.ts create mode 100644 packages/api/router/fieldOpt/query.attributesByCategory.handler.ts create mode 100644 packages/api/router/fieldOpt/query.attributesByCategory.schema.ts create mode 100644 packages/api/router/fieldOpt/query.countries.handler.ts create mode 100644 packages/api/router/fieldOpt/query.countries.schema.ts create mode 100644 packages/api/router/fieldOpt/query.countryGovDistMap.handler.ts create mode 100644 packages/api/router/fieldOpt/query.govDistsByCountry.handler.ts create mode 100644 packages/api/router/fieldOpt/query.govDistsByCountry.schema.ts create mode 100644 packages/api/router/fieldOpt/query.govDistsByCountryNoSub.handler.ts create mode 100644 packages/api/router/fieldOpt/query.govDistsByCountryNoSub.schema.ts create mode 100644 packages/api/router/fieldOpt/query.languages.handler.ts create mode 100644 packages/api/router/fieldOpt/query.languages.schema.ts create mode 100644 packages/api/router/fieldOpt/query.phoneTypes.handler.ts create mode 100644 packages/api/router/fieldOpt/query.userTitle.handler.ts create mode 100644 packages/api/router/fieldOpt/schemas.ts diff --git a/packages/api/router/fieldOpt/index.ts b/packages/api/router/fieldOpt/index.ts index fb02f69097..85a5f3f064 100644 --- a/packages/api/router/fieldOpt/index.ts +++ b/packages/api/router/fieldOpt/index.ts @@ -1,11 +1,22 @@ -import flush from 'just-flush' -import { type SetOptional } from 'type-fest' import { z } from 'zod' -import { type AttributesByCategory } from '@weareinreach/db/client' import { defineRouter, publicProcedure } from '~api/lib/trpc' -import { serviceAreaSelect2, serviceAreaSelectNoSub } from '~api/schemas/selects/location' +import * as schema from './schemas' + +const HandlerCache: Partial = {} + +type FieldOptHandlerCache = { + govDistsByCountry: typeof import('./query.govDistsByCountry.handler').govDistsByCountry + govDistsByCountryNoSub: typeof import('./query.govDistsByCountryNoSub.handler').govDistsByCountryNoSub + phoneTypes: typeof import('./query.phoneTypes.handler').phoneTypes + attributesByCategory: typeof import('./query.attributesByCategory.handler').attributesByCategory + attributeCategories: typeof import('./query.attributeCategories.handler').attributeCategories + languages: typeof import('./query.languages.handler').languages + countries: typeof import('./query.countries.handler').countries + userTitle: typeof import('./query.userTitle.handler').userTitle + countryGovDistMap: typeof import('./query.countryGovDistMap.handler').countryGovDistMap +} export const fieldOptRouter = defineRouter({ /** All government districts by country (active for org listings). Gives up to 2 levels of sub-districts */ govDistsByCountry: publicProcedure @@ -13,195 +24,78 @@ export const fieldOptRouter = defineRouter({ description: 'All government districts by country (active for org listings). Gives 2 levels of sub-districts', }) - .input( - z - .object({ - cca2: z.string().optional().describe('Country CCA2 code'), - activeForOrgs: z.boolean().nullish().default(true), - activeForSuggest: z.boolean().nullish(), - }) - .optional() - ) + .input(schema.ZGovDistsByCountrySchema) .query(async ({ ctx, input }) => { - const data = await ctx.prisma.country.findMany({ - where: { - cca2: input?.cca2, - activeForOrgs: input?.activeForOrgs ?? undefined, - activeForSuggest: input?.activeForSuggest ?? undefined, - }, - select: serviceAreaSelect2, - orderBy: { - cca2: 'asc', - }, - }) - return data + if (!HandlerCache.govDistsByCountry) + HandlerCache.govDistsByCountry = await import('./query.govDistsByCountry.handler').then( + (mod) => mod.govDistsByCountry + ) + if (!HandlerCache.govDistsByCountry) throw new Error('Failed to load handler') + return HandlerCache.govDistsByCountry({ ctx, input }) }), govDistsByCountryNoSub: publicProcedure .meta({ description: 'All government districts by country (active for org listings).', }) - .input( - z - .object({ - cca2: z.string().optional().describe('Country CCA2 code'), - activeForOrgs: z.boolean().nullish().default(true), - activeForSuggest: z.boolean().nullish(), - }) - .optional() - ) + .input(schema.ZGovDistsByCountryNoSubSchema) .query(async ({ ctx, input }) => { - const data = await ctx.prisma.country.findMany({ - where: { - cca2: input?.cca2, - activeForOrgs: input?.activeForOrgs ?? undefined, - activeForSuggest: input?.activeForSuggest ?? undefined, - }, - select: serviceAreaSelectNoSub, - orderBy: { - cca2: 'asc', - }, - }) - return data + if (!HandlerCache.govDistsByCountryNoSub) + HandlerCache.govDistsByCountryNoSub = await import('./query.govDistsByCountryNoSub.handler').then( + (mod) => mod.govDistsByCountryNoSub + ) + if (!HandlerCache.govDistsByCountryNoSub) throw new Error('Failed to load handler') + return HandlerCache.govDistsByCountryNoSub({ ctx, input }) }), - phoneTypes: publicProcedure.query(async ({ ctx }) => - ctx.prisma.phoneType.findMany({ - where: { active: true }, - select: { - id: true, - tsKey: true, - tsNs: true, - }, - orderBy: { type: 'asc' }, - }) - ), + phoneTypes: publicProcedure.query(async () => { + if (!HandlerCache.phoneTypes) + HandlerCache.phoneTypes = await import('./query.phoneTypes.handler').then((mod) => mod.phoneTypes) + if (!HandlerCache.phoneTypes) throw new Error('Failed to load handler') + return HandlerCache.phoneTypes() + }), attributesByCategory: publicProcedure - .input(z.string().or(z.string().array()).optional().describe('categoryName')) + .input(schema.ZAttributesByCategorySchema) .query(async ({ ctx, input }) => { - const where = Array.isArray(input) - ? { categoryName: { in: input } } - : typeof input === 'string' - ? { categoryName: input } - : undefined - const result = await ctx.prisma.attributesByCategory.findMany({ - where, - orderBy: [{ categoryName: 'asc' }, { attributeName: 'asc' }], - }) - type FlushedAttributesByCategory = SetOptional< - AttributesByCategory, - 'interpolationValues' | 'icon' | 'iconBg' | 'badgeRender' | 'dataSchema' | 'dataSchemaName' - > - const flushedResults = result.map((item) => - flush(item) - ) as FlushedAttributesByCategory[] - return flushedResults + if (!HandlerCache.attributesByCategory) + HandlerCache.attributesByCategory = await import('./query.attributesByCategory.handler').then( + (mod) => mod.attributesByCategory + ) + if (!HandlerCache.attributesByCategory) throw new Error('Failed to load handler') + return HandlerCache.attributesByCategory({ ctx, input }) }), - attributeCategories: publicProcedure.input(z.string().array().optional()).query(async ({ ctx, input }) => - ctx.prisma.attributeCategory.findMany({ - where: { active: true, ...(input?.length ? { tag: { in: input } } : {}) }, - select: { - id: true, - tag: true, - name: true, - icon: true, - intDesc: true, - }, - orderBy: { tag: 'asc' }, - }) - ), - languages: publicProcedure - .input(z.object({ activelyTranslated: z.boolean(), localeCode: z.string() }).partial().optional()) - .query(async ({ ctx, input }) => - ctx.prisma.language.findMany({ - where: input, - select: { - id: true, - languageName: true, - localeCode: true, - iso6392: true, - nativeName: true, - activelyTranslated: true, - }, - orderBy: { languageName: 'asc' }, - }) - ), - countries: publicProcedure - .input( - z - .object({ - where: z.object({ activeForOrgs: z.boolean(), cca2: z.string() }), - includeGeo: z.object({ wkt: z.boolean(), json: z.boolean() }), - }) - .deepPartial() - .optional() - ) + attributeCategories: publicProcedure + .input(schema.ZAttributeCategoriesSchema) .query(async ({ ctx, input }) => { - const { where, includeGeo } = input ?? {} - const result = await ctx.prisma.country.findMany({ - where, - select: { - id: true, - cca2: true, - name: true, - dialCode: true, - flag: true, - tsKey: true, - tsNs: true, - activeForOrgs: true, - }, - orderBy: { - name: 'asc', - }, - }) - type CountryResult = (typeof result)[number][] - return result as CountryResult + if (!HandlerCache.attributeCategories) + HandlerCache.attributeCategories = await import('./query.attributeCategories.handler').then( + (mod) => mod.attributeCategories + ) + if (!HandlerCache.attributeCategories) throw new Error('Failed to load handler') + return HandlerCache.attributeCategories({ ctx, input }) }), - userTitle: publicProcedure.query(async ({ ctx }) => - ctx.prisma.userTitle.findMany({ where: { searchable: true }, select: { id: true, title: true } }) - ), + languages: publicProcedure.input(schema.ZLanguagesSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.languages) + HandlerCache.languages = await import('./query.languages.handler').then((mod) => mod.languages) + if (!HandlerCache.languages) throw new Error('Failed to load handler') + return HandlerCache.languages({ ctx, input }) + }), + countries: publicProcedure.input(schema.ZCountriesSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.countries) + HandlerCache.countries = await import('./query.countries.handler').then((mod) => mod.countries) + if (!HandlerCache.countries) throw new Error('Failed to load handler') + return HandlerCache.countries({ ctx, input }) + }), + userTitle: publicProcedure.query(async () => { + if (!HandlerCache.userTitle) + HandlerCache.userTitle = await import('./query.userTitle.handler').then((mod) => mod.userTitle) + if (!HandlerCache.userTitle) throw new Error('Failed to load handler') + return HandlerCache.userTitle() + }), countryGovDistMap: publicProcedure.query(async ({ ctx }) => { - const fields = { id: true, tsKey: true, tsNs: true } - - const countries = await ctx.prisma.country.findMany({ - where: { activeForOrgs: true }, - select: { - ...fields, - govDist: { select: fields }, - }, - }) - const govDists = await ctx.prisma.govDist.findMany({ - select: { - ...fields, - subDistricts: { - select: fields, - }, - parent: { select: fields }, - country: { select: fields }, - }, - }) - const resultMap = new Map([ - ...(countries.map(({ govDist, ...rest }) => [rest.id, { ...rest, children: govDist }]) satisfies [ - string, - CountryGovDistMapItem, - ][]), - ...(govDists.map(({ subDistricts, parent, country, ...rest }) => [ - rest.id, - { ...rest, children: subDistricts, parent: parent ? { ...parent, parent: country } : country }, - ]) satisfies [string, CountryGovDistMapItem][]), - ]) - return resultMap + if (!HandlerCache.countryGovDistMap) + HandlerCache.countryGovDistMap = await import('./query.countryGovDistMap.handler').then( + (mod) => mod.countryGovDistMap + ) + if (!HandlerCache.countryGovDistMap) throw new Error('Failed to load handler') + return HandlerCache.countryGovDistMap() }), }) - -interface CountryGovDistMapItemBasic { - id: string - tsKey: string - tsNs: string -} - -interface CountryGovDistMapItem { - id: string - tsKey: string - tsNs: string - children: CountryGovDistMapItemBasic[] - parent?: CountryGovDistMapItemBasic & { parent?: CountryGovDistMapItemBasic } -} diff --git a/packages/api/router/fieldOpt/query.attributeCategories.handler.ts b/packages/api/router/fieldOpt/query.attributeCategories.handler.ts new file mode 100644 index 0000000000..af1932ceeb --- /dev/null +++ b/packages/api/router/fieldOpt/query.attributeCategories.handler.ts @@ -0,0 +1,24 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TAttributeCategoriesSchema } from './query.attributeCategories.schema' + +export const attributeCategories = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const results = await prisma.attributeCategory.findMany({ + where: { active: true, ...(input?.length ? { tag: { in: input } } : {}) }, + select: { + id: true, + tag: true, + name: true, + icon: true, + intDesc: true, + }, + orderBy: { tag: 'asc' }, + }) + return results + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/fieldOpt/query.attributeCategories.schema.ts b/packages/api/router/fieldOpt/query.attributeCategories.schema.ts new file mode 100644 index 0000000000..802a2babbf --- /dev/null +++ b/packages/api/router/fieldOpt/query.attributeCategories.schema.ts @@ -0,0 +1,4 @@ +import { z } from 'zod' + +export const ZAttributeCategoriesSchema = z.string().array().optional() +export type TAttributeCategoriesSchema = z.infer diff --git a/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts b/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts new file mode 100644 index 0000000000..96dc71fc91 --- /dev/null +++ b/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts @@ -0,0 +1,34 @@ +import flush from 'just-flush' +import { type SetOptional } from 'type-fest' + +import { prisma } from '@weareinreach/db' +import { type AttributesByCategory } from '@weareinreach/db/client' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TAttributesByCategorySchema } from './query.attributesByCategory.schema' + +export const attributesByCategory = async ({ input }: TRPCHandlerParams) => { + try { + const where = Array.isArray(input) + ? { categoryName: { in: input } } + : typeof input === 'string' + ? { categoryName: input } + : undefined + const result = await prisma.attributesByCategory.findMany({ + where, + orderBy: [{ categoryName: 'asc' }, { attributeName: 'asc' }], + }) + + const flushedResults = result.map((item) => + flush(item) + ) as FlushedAttributesByCategory[] + return flushedResults + } catch (error) { + handleError(error) + } +} +type FlushedAttributesByCategory = SetOptional< + AttributesByCategory, + 'interpolationValues' | 'icon' | 'iconBg' | 'badgeRender' | 'dataSchema' | 'dataSchemaName' +> diff --git a/packages/api/router/fieldOpt/query.attributesByCategory.schema.ts b/packages/api/router/fieldOpt/query.attributesByCategory.schema.ts new file mode 100644 index 0000000000..8fc56977e4 --- /dev/null +++ b/packages/api/router/fieldOpt/query.attributesByCategory.schema.ts @@ -0,0 +1,8 @@ +import { z } from 'zod' + +export const ZAttributesByCategorySchema = z + .string() + .or(z.string().array()) + .optional() + .describe('categoryName') +export type TAttributesByCategorySchema = z.infer diff --git a/packages/api/router/fieldOpt/query.countries.handler.ts b/packages/api/router/fieldOpt/query.countries.handler.ts new file mode 100644 index 0000000000..5671ad465f --- /dev/null +++ b/packages/api/router/fieldOpt/query.countries.handler.ts @@ -0,0 +1,31 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCountriesSchema } from './query.countries.schema' + +export const countries = async ({ input }: TRPCHandlerParams) => { + try { + const { where } = input ?? {} + const result = await prisma.country.findMany({ + where, + select: { + id: true, + cca2: true, + name: true, + dialCode: true, + flag: true, + tsKey: true, + tsNs: true, + activeForOrgs: true, + }, + orderBy: { + name: 'asc', + }, + }) + type CountryResult = (typeof result)[number][] + return result as CountryResult + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/fieldOpt/query.countries.schema.ts b/packages/api/router/fieldOpt/query.countries.schema.ts new file mode 100644 index 0000000000..f1c4a471cc --- /dev/null +++ b/packages/api/router/fieldOpt/query.countries.schema.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' + +export const ZCountriesSchema = z + .object({ + where: z.object({ activeForOrgs: z.boolean(), cca2: z.string() }), + // includeGeo: z.object({ wkt: z.boolean(), json: z.boolean() }), + }) + .deepPartial() + .optional() +export type TCountriesSchema = z.infer diff --git a/packages/api/router/fieldOpt/query.countryGovDistMap.handler.ts b/packages/api/router/fieldOpt/query.countryGovDistMap.handler.ts new file mode 100644 index 0000000000..664663dcd7 --- /dev/null +++ b/packages/api/router/fieldOpt/query.countryGovDistMap.handler.ts @@ -0,0 +1,53 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' + +export const countryGovDistMap = async () => { + try { + const fields = { id: true, tsKey: true, tsNs: true } + + const countries = await prisma.country.findMany({ + where: { activeForOrgs: true }, + select: { + ...fields, + govDist: { select: fields }, + }, + }) + const govDists = await prisma.govDist.findMany({ + select: { + ...fields, + subDistricts: { + select: fields, + }, + parent: { select: fields }, + country: { select: fields }, + }, + }) + const resultMap = new Map([ + ...(countries.map(({ govDist, ...rest }) => [rest.id, { ...rest, children: govDist }]) satisfies [ + string, + CountryGovDistMapItem, + ][]), + ...(govDists.map(({ subDistricts, parent, country, ...rest }) => [ + rest.id, + { ...rest, children: subDistricts, parent: parent ? { ...parent, parent: country } : country }, + ]) satisfies [string, CountryGovDistMapItem][]), + ]) + return resultMap + } catch (error) { + handleError(error) + } +} + +interface CountryGovDistMapItemBasic { + id: string + tsKey: string + tsNs: string +} + +interface CountryGovDistMapItem { + id: string + tsKey: string + tsNs: string + children: CountryGovDistMapItemBasic[] + parent?: CountryGovDistMapItemBasic & { parent?: CountryGovDistMapItemBasic } +} diff --git a/packages/api/router/fieldOpt/query.govDistsByCountry.handler.ts b/packages/api/router/fieldOpt/query.govDistsByCountry.handler.ts new file mode 100644 index 0000000000..a09dbfe6c5 --- /dev/null +++ b/packages/api/router/fieldOpt/query.govDistsByCountry.handler.ts @@ -0,0 +1,65 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGovDistsByCountrySchema } from './query.govDistsByCountry.schema' + +export const govDistsByCountry = async ({ input }: TRPCHandlerParams) => { + try { + const data = await prisma.country.findMany({ + where: { + cca2: input?.cca2, + activeForOrgs: input?.activeForOrgs ?? undefined, + activeForSuggest: input?.activeForSuggest ?? undefined, + }, + select: { + id: true, + tsKey: true, + tsNs: true, + cca2: true, + flag: true, + govDist: { + where: { isPrimary: true }, + select: { + id: true, + tsKey: true, + tsNs: true, + abbrev: true, + govDistType: { + select: { tsKey: true, tsNs: true }, + }, + subDistricts: { + select: { + id: true, + tsKey: true, + tsNs: true, + govDistType: { + select: { tsKey: true, tsNs: true }, + }, + subDistricts: { + select: { + id: true, + tsKey: true, + tsNs: true, + govDistType: { + select: { tsKey: true, tsNs: true }, + }, + }, + orderBy: { name: 'asc' }, + }, + }, + orderBy: { name: 'asc' }, + }, + }, + orderBy: { name: 'asc' }, + }, + }, + orderBy: { + cca2: 'asc', + }, + }) + return data + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/fieldOpt/query.govDistsByCountry.schema.ts b/packages/api/router/fieldOpt/query.govDistsByCountry.schema.ts new file mode 100644 index 0000000000..22dcf8e572 --- /dev/null +++ b/packages/api/router/fieldOpt/query.govDistsByCountry.schema.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' + +export const ZGovDistsByCountrySchema = z + .object({ + cca2: z.string().optional().describe('Country CCA2 code'), + activeForOrgs: z.boolean().nullish().default(true), + activeForSuggest: z.boolean().nullish(), + }) + .optional() +export type TGovDistsByCountrySchema = z.infer diff --git a/packages/api/router/fieldOpt/query.govDistsByCountryNoSub.handler.ts b/packages/api/router/fieldOpt/query.govDistsByCountryNoSub.handler.ts new file mode 100644 index 0000000000..0bb419eadd --- /dev/null +++ b/packages/api/router/fieldOpt/query.govDistsByCountryNoSub.handler.ts @@ -0,0 +1,43 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGovDistsByCountryNoSubSchema } from './query.govDistsByCountryNoSub.schema' + +export const govDistsByCountryNoSub = async ({ input }: TRPCHandlerParams) => { + try { + const data = await prisma.country.findMany({ + where: { + cca2: input?.cca2, + activeForOrgs: input?.activeForOrgs ?? undefined, + activeForSuggest: input?.activeForSuggest ?? undefined, + }, + select: { + id: true, + tsKey: true, + tsNs: true, + cca2: true, + flag: true, + govDist: { + where: { isPrimary: true }, + select: { + id: true, + tsKey: true, + tsNs: true, + abbrev: true, + govDistType: { + select: { tsKey: true, tsNs: true }, + }, + }, + orderBy: { name: 'asc' }, + }, + }, + orderBy: { + cca2: 'asc', + }, + }) + return data + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/fieldOpt/query.govDistsByCountryNoSub.schema.ts b/packages/api/router/fieldOpt/query.govDistsByCountryNoSub.schema.ts new file mode 100644 index 0000000000..7bfb9c22e6 --- /dev/null +++ b/packages/api/router/fieldOpt/query.govDistsByCountryNoSub.schema.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' + +export const ZGovDistsByCountryNoSubSchema = z + .object({ + cca2: z.string().optional().describe('Country CCA2 code'), + activeForOrgs: z.boolean().nullish().default(true), + activeForSuggest: z.boolean().nullish(), + }) + .optional() +export type TGovDistsByCountryNoSubSchema = z.infer diff --git a/packages/api/router/fieldOpt/query.languages.handler.ts b/packages/api/router/fieldOpt/query.languages.handler.ts new file mode 100644 index 0000000000..d3f24f12d9 --- /dev/null +++ b/packages/api/router/fieldOpt/query.languages.handler.ts @@ -0,0 +1,25 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TLanguagesSchema } from './query.languages.schema' + +export const languages = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const results = await prisma.language.findMany({ + where: input, + select: { + id: true, + languageName: true, + localeCode: true, + iso6392: true, + nativeName: true, + activelyTranslated: true, + }, + orderBy: { languageName: 'asc' }, + }) + return results + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/fieldOpt/query.languages.schema.ts b/packages/api/router/fieldOpt/query.languages.schema.ts new file mode 100644 index 0000000000..efe25d48f9 --- /dev/null +++ b/packages/api/router/fieldOpt/query.languages.schema.ts @@ -0,0 +1,7 @@ +import { z } from 'zod' + +export const ZLanguagesSchema = z + .object({ activelyTranslated: z.boolean(), localeCode: z.string() }) + .partial() + .optional() +export type TLanguagesSchema = z.infer diff --git a/packages/api/router/fieldOpt/query.phoneTypes.handler.ts b/packages/api/router/fieldOpt/query.phoneTypes.handler.ts new file mode 100644 index 0000000000..020e7c79ad --- /dev/null +++ b/packages/api/router/fieldOpt/query.phoneTypes.handler.ts @@ -0,0 +1,19 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' + +export const phoneTypes = async () => { + try { + const result = await prisma.phoneType.findMany({ + where: { active: true }, + select: { + id: true, + tsKey: true, + tsNs: true, + }, + orderBy: { type: 'asc' }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/fieldOpt/query.userTitle.handler.ts b/packages/api/router/fieldOpt/query.userTitle.handler.ts new file mode 100644 index 0000000000..367f2babc8 --- /dev/null +++ b/packages/api/router/fieldOpt/query.userTitle.handler.ts @@ -0,0 +1,14 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' + +export const userTitle = async () => { + try { + const results = await prisma.userTitle.findMany({ + where: { searchable: true }, + select: { id: true, title: true }, + }) + return results + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/fieldOpt/schemas.ts b/packages/api/router/fieldOpt/schemas.ts new file mode 100644 index 0000000000..b9e814dbd4 --- /dev/null +++ b/packages/api/router/fieldOpt/schemas.ts @@ -0,0 +1,8 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './query.attributeCategories.schema' +export * from './query.attributesByCategory.schema' +export * from './query.countries.schema' +export * from './query.govDistsByCountry.schema' +export * from './query.govDistsByCountryNoSub.schema' +export * from './query.languages.schema' +// codegen:end From d1e37bfc63ed52867427f2597275adba9aabe57e Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:36:40 -0400 Subject: [PATCH 23/63] geo router caching/lazy load --- packages/api/google/index.ts | 3 + packages/api/router/geo/index.ts | 74 +++++-------------- .../router/geo/query.autocomplete.handler.ts | 43 +++++++++++ .../router/geo/query.autocomplete.schema.ts | 9 +++ .../router/geo/query.geoByPlaceId.handler.ts | 23 ++++++ .../router/geo/query.geoByPlaceId.schema.ts | 4 + packages/api/router/geo/schemas.ts | 4 + 7 files changed, 105 insertions(+), 55 deletions(-) create mode 100644 packages/api/google/index.ts create mode 100644 packages/api/router/geo/query.autocomplete.handler.ts create mode 100644 packages/api/router/geo/query.autocomplete.schema.ts create mode 100644 packages/api/router/geo/query.geoByPlaceId.handler.ts create mode 100644 packages/api/router/geo/query.geoByPlaceId.schema.ts create mode 100644 packages/api/router/geo/schemas.ts diff --git a/packages/api/google/index.ts b/packages/api/google/index.ts new file mode 100644 index 0000000000..ebc6bbd338 --- /dev/null +++ b/packages/api/google/index.ts @@ -0,0 +1,3 @@ +import { Client } from '@googlemaps/google-maps-services-js' + +export const googleMapsApi = new Client() diff --git a/packages/api/router/geo/index.ts b/packages/api/router/geo/index.ts index da94c3b71f..96494c135d 100644 --- a/packages/api/router/geo/index.ts +++ b/packages/api/router/geo/index.ts @@ -1,61 +1,25 @@ -/* eslint-disable turbo/no-undeclared-env-vars */ -/* eslint-disable node/no-process-env */ -import { - Client, - type PlaceAutocompleteRequest, - type PlaceAutocompleteType, -} from '@googlemaps/google-maps-services-js' -import { z } from 'zod' - -import { googleAPIResponseHandler } from '~api/lib/googleHandler' import { defineRouter, publicProcedure } from '~api/lib/trpc' -import { autocompleteResponse, geocodeResponse } from '~api/schemas/thirdParty/googleGeo' -const google = new Client() +import * as schema from './schemas' -export const geoRouter = defineRouter({ - autocomplete: publicProcedure - .input( - z.object({ - search: z.string(), - locale: z.string().optional(), - cityOnly: z.boolean().default(false).optional(), - fullAddress: z.boolean().default(false).optional(), - }) - ) - .query(async ({ input }) => { - const types = input.cityOnly - ? ['(cities)'] - : input.fullAddress - ? ['address'] - : ([ - 'administrative_area_level_2', - 'administrative_area_level_3', - 'neighborhood', - 'locality', - 'postal_code', - ] as unknown as PlaceAutocompleteType) +const HandlerCache: Partial = {} + +type GeoHandlerCache = { + autocomplete: typeof import('./query.autocomplete.handler').autocomplete + geoByPlaceId: typeof import('./query.geoByPlaceId.handler').geoByPlaceId +} - const { data } = await google.placeAutocomplete({ - params: { - key: process.env.GOOGLE_PLACES_API_KEY as string, - input: input.search, - language: input.locale, - types, - locationbias: 'ipbias', - }, - } as PlaceAutocompleteRequest) - const parsedData = autocompleteResponse.parse(data) - return googleAPIResponseHandler(parsedData, data) - }), - geoByPlaceId: publicProcedure.input(z.string()).query(async ({ input }) => { - const { data } = await google.geocode({ - params: { - key: process.env.GOOGLE_PLACES_API_KEY as string, - place_id: input, - }, - }) - const parsedData = geocodeResponse.parse(data) - return googleAPIResponseHandler(parsedData, data) +export const geoRouter = defineRouter({ + autocomplete: publicProcedure.input(schema.ZAutocompleteSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.autocomplete) + HandlerCache.autocomplete = await import('./query.autocomplete.handler').then((mod) => mod.autocomplete) + if (!HandlerCache.autocomplete) throw new Error('Failed to load handler') + return HandlerCache.autocomplete({ ctx, input }) + }), + geoByPlaceId: publicProcedure.input(schema.ZGeoByPlaceIdSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.geoByPlaceId) + HandlerCache.geoByPlaceId = await import('./query.geoByPlaceId.handler').then((mod) => mod.geoByPlaceId) + if (!HandlerCache.geoByPlaceId) throw new Error('Failed to load handler') + return HandlerCache.geoByPlaceId({ ctx, input }) }), }) diff --git a/packages/api/router/geo/query.autocomplete.handler.ts b/packages/api/router/geo/query.autocomplete.handler.ts new file mode 100644 index 0000000000..d43de5fd47 --- /dev/null +++ b/packages/api/router/geo/query.autocomplete.handler.ts @@ -0,0 +1,43 @@ +/* eslint-disable node/no-process-env */ +import { + type PlaceAutocompleteRequest, + type PlaceAutocompleteType, +} from '@googlemaps/google-maps-services-js' + +import { googleMapsApi } from '~api/google' +import { handleError } from '~api/lib/errorHandler' +import { googleAPIResponseHandler } from '~api/lib/googleHandler' +import { autocompleteResponse } from '~api/schemas/thirdParty/googleGeo' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TAutocompleteSchema } from './query.autocomplete.schema' + +export const autocomplete = async ({ input }: TRPCHandlerParams) => { + try { + const types = input.cityOnly + ? ['(cities)'] + : input.fullAddress + ? ['address'] + : ([ + 'administrative_area_level_2', + 'administrative_area_level_3', + 'neighborhood', + 'locality', + 'postal_code', + ] as unknown as PlaceAutocompleteType) + + const { data } = await googleMapsApi.placeAutocomplete({ + params: { + key: process.env.GOOGLE_PLACES_API_KEY as string, + input: input.search, + language: input.locale, + types, + locationbias: 'ipbias', + }, + } as PlaceAutocompleteRequest) + const parsedData = autocompleteResponse.parse(data) + return googleAPIResponseHandler(parsedData, data) + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/geo/query.autocomplete.schema.ts b/packages/api/router/geo/query.autocomplete.schema.ts new file mode 100644 index 0000000000..0d6b60c6aa --- /dev/null +++ b/packages/api/router/geo/query.autocomplete.schema.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' + +export const ZAutocompleteSchema = z.object({ + search: z.string(), + locale: z.string().optional(), + cityOnly: z.boolean().default(false).optional(), + fullAddress: z.boolean().default(false).optional(), +}) +export type TAutocompleteSchema = z.infer diff --git a/packages/api/router/geo/query.geoByPlaceId.handler.ts b/packages/api/router/geo/query.geoByPlaceId.handler.ts new file mode 100644 index 0000000000..1e8f1a5959 --- /dev/null +++ b/packages/api/router/geo/query.geoByPlaceId.handler.ts @@ -0,0 +1,23 @@ +/* eslint-disable node/no-process-env */ +import { googleMapsApi } from '~api/google' +import { handleError } from '~api/lib/errorHandler' +import { googleAPIResponseHandler } from '~api/lib/googleHandler' +import { geocodeResponse } from '~api/schemas/thirdParty/googleGeo' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGeoByPlaceIdSchema } from './query.geoByPlaceId.schema' + +export const geoByPlaceId = async ({ input }: TRPCHandlerParams) => { + try { + const { data } = await googleMapsApi.geocode({ + params: { + key: process.env.GOOGLE_PLACES_API_KEY as string, + place_id: input, + }, + }) + const parsedData = geocodeResponse.parse(data) + return googleAPIResponseHandler(parsedData, data) + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/geo/query.geoByPlaceId.schema.ts b/packages/api/router/geo/query.geoByPlaceId.schema.ts new file mode 100644 index 0000000000..ae40a4afb4 --- /dev/null +++ b/packages/api/router/geo/query.geoByPlaceId.schema.ts @@ -0,0 +1,4 @@ +import { z } from 'zod' + +export const ZGeoByPlaceIdSchema = z.string() +export type TGeoByPlaceIdSchema = z.infer diff --git a/packages/api/router/geo/schemas.ts b/packages/api/router/geo/schemas.ts new file mode 100644 index 0000000000..e9736936b4 --- /dev/null +++ b/packages/api/router/geo/schemas.ts @@ -0,0 +1,4 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './query.autocomplete.schema' +export * from './query.geoByPlaceId.schema' +// codegen:end From fb94bd68e242f438aca7670664ef74cc3918a7e4 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 17:25:29 -0400 Subject: [PATCH 24/63] internal note router caching/lazy load --- packages/api/router/internalNote/index.ts | 50 +++++----- .../internalNote/mutation.create.handler.ts | 25 +++++ .../internalNote/mutation.create.schema.ts | 93 +++++++++++++++++++ .../router/internalNote/query.byId.handler.ts | 14 +++ .../router/internalNote/query.byId.schema.ts | 6 ++ .../query.getAllForRecord.handler.ts | 14 +++ .../query.getAllForRecord.schema.ts | 41 ++++++++ packages/api/router/internalNote/schemas.ts | 5 + 8 files changed, 227 insertions(+), 21 deletions(-) create mode 100644 packages/api/router/internalNote/mutation.create.handler.ts create mode 100644 packages/api/router/internalNote/mutation.create.schema.ts create mode 100644 packages/api/router/internalNote/query.byId.handler.ts create mode 100644 packages/api/router/internalNote/query.byId.schema.ts create mode 100644 packages/api/router/internalNote/query.getAllForRecord.handler.ts create mode 100644 packages/api/router/internalNote/query.getAllForRecord.schema.ts create mode 100644 packages/api/router/internalNote/schemas.ts diff --git a/packages/api/router/internalNote/index.ts b/packages/api/router/internalNote/index.ts index 188a69bc78..ab12a3bee6 100644 --- a/packages/api/router/internalNote/index.ts +++ b/packages/api/router/internalNote/index.ts @@ -1,34 +1,42 @@ -import { z } from 'zod' - import { defineRouter, staffProcedure } from '~api/lib/trpc' -import { CreateNote, GetNoteByRecord } from '~api/schemas/internalNote' +import * as schema from './schemas' + +const HandlerCache: Partial = {} + +type InternalNoteHandlerCache = { + byId: typeof import('./query.byId.handler').byId + getAllForRecord: typeof import('./query.getAllForRecord.handler').getAllForRecord + create: typeof import('./mutation.create.handler').create +} export const internalNoteRouter = defineRouter({ byId: staffProcedure .meta({ hasPerm: 'internalNotesRead' }) - .input(z.string()) - .query(async ({ ctx, input }) => ctx.prisma.internalNote.findFirstOrThrow({ where: { id: input } })), + .input(schema.ZByIdSchema) + .query(async ({ ctx, input }) => { + if (!HandlerCache.byId) HandlerCache.byId = await import('./query.byId.handler').then((mod) => mod.byId) + if (!HandlerCache.byId) throw new Error('Failed to load handler') + return HandlerCache.byId({ ctx, input }) + }), getAllForRecord: staffProcedure .meta({ hasPerm: 'internalNotesRead' }) - .input(GetNoteByRecord) - .query(async ({ ctx, input }) => ctx.prisma.internalNote.findMany({ where: input })), + .input(schema.ZGetAllForRecordSchema) + .query(async ({ ctx, input }) => { + if (!HandlerCache.getAllForRecord) + HandlerCache.getAllForRecord = await import('./query.getAllForRecord.handler').then( + (mod) => mod.getAllForRecord + ) + if (!HandlerCache.getAllForRecord) throw new Error('Failed to load handler') + return HandlerCache.getAllForRecord({ ctx, input }) + }), create: staffProcedure .meta({ hasPerm: 'internalNotesWrite' }) - .input(CreateNote().inputSchema) + .input(schema.ZCreateSchema().inputSchema) .mutation(async ({ ctx, input }) => { - const inputData = { - actorId: ctx.session.user.id, - operation: 'CREATE', - data: input, - } - const { internalNote, auditLog } = CreateNote().dataParser.parse(inputData) - - const results = await ctx.prisma.$transaction(async (tx) => { - const note = await tx.internalNote.create(internalNote) - const log = await tx.auditLog.create(auditLog) - return { note, log } - }) - return results.note.id + if (!HandlerCache.create) + HandlerCache.create = await import('./mutation.create.handler').then((mod) => mod.create) + if (!HandlerCache.create) throw new Error('Failed to load handler') + return HandlerCache.create({ ctx, input }) }), }) diff --git a/packages/api/router/internalNote/mutation.create.handler.ts b/packages/api/router/internalNote/mutation.create.handler.ts new file mode 100644 index 0000000000..dde8dd1cc9 --- /dev/null +++ b/packages/api/router/internalNote/mutation.create.handler.ts @@ -0,0 +1,25 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateSchema, ZCreateSchema } from './mutation.create.schema' + +export const create = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const inputData = { + actorId: ctx.session.user.id, + operation: 'CREATE', + data: input, + } + const { internalNote, auditLog } = ZCreateSchema().dataParser.parse(inputData) + + const results = await prisma.$transaction(async (tx) => { + const note = await tx.internalNote.create(internalNote) + const log = await tx.auditLog.create(auditLog) + return { note, log } + }) + return results.note.id + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/internalNote/mutation.create.schema.ts b/packages/api/router/internalNote/mutation.create.schema.ts new file mode 100644 index 0000000000..684ffc81e9 --- /dev/null +++ b/packages/api/router/internalNote/mutation.create.schema.ts @@ -0,0 +1,93 @@ +import flush from 'just-flush' +import { z } from 'zod' + +import { generateId, Prisma } from '@weareinreach/db' +import { CreateBase } from '~api/schemaBase/create' +import { GenerateAuditLog } from '~api/schemas/create/auditLog' +import { prefixedId } from '~api/schemas/idPrefix' + +const nonEmptyString = z + .string() + .transform((val) => (String(val).trim() === '' ? undefined : String(val).trim())) + +export const ZCreateSchema = () => { + const { dataParser: parser, inputSchema } = CreateBase( + z.object({ + text: z.string(), + attributeId: prefixedId('attribute').optional(), + attributeCategoryId: prefixedId('attributeCategory').optional(), + attributeSupplementId: prefixedId('attributeSupplement').optional(), + attributeSupplementDataSchemaId: prefixedId('attributeSupplementDataSchema').optional(), + countryId: prefixedId('country').optional(), + govDistId: prefixedId('govDist').optional(), + govDistTypeId: prefixedId('govDistType').optional(), + languageId: prefixedId('language').optional(), + organizationId: prefixedId('organization').optional(), + orgEmailId: prefixedId('orgEmail').optional(), + orgHoursId: prefixedId('orgHours').optional(), + orgLocationId: prefixedId('orgLocation').optional(), + orgPhoneId: prefixedId('orgPhone').optional(), + orgPhotoId: prefixedId('orgPhoto').optional(), + orgReviewId: prefixedId('orgReview').optional(), + orgServiceId: prefixedId('orgService').optional(), + orgSocialMediaId: prefixedId('orgSocialMedia').optional(), + orgWebsiteId: prefixedId('orgWebsite').optional(), + outsideApiId: prefixedId('outsideAPI').optional(), + permissionId: prefixedId('permission').optional(), + phoneTypeId: prefixedId('phoneType').optional(), + serviceCategoryId: prefixedId('serviceCategory').optional(), + serviceTagId: prefixedId('serviceTag').optional(), + socialMediaLinkId: prefixedId('socialMediaLink').optional(), + socialMediaServiceId: prefixedId('socialMediaService').optional(), + sourceId: prefixedId('source').optional(), + suggestionId: prefixedId('suggestion').optional(), + outsideAPIServiceService: nonEmptyString.optional(), + translationKey: nonEmptyString.optional(), + translationNs: nonEmptyString.optional(), + translationNamespaceName: nonEmptyString.optional(), + }) + ) + const dataParser = parser + .superRefine(({ data }, ctx) => { + const links = flush(data) + const keys = Object.keys(links).filter((key) => key !== 'text') + if (keys.length === 0) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'At least one ID key required', + }) + return z.NEVER + } + if (keys.includes('translationKey') || keys.includes('translationNs')) { + if (!keys.includes('translationKey') || !keys.includes('translationNs')) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: '`translationKey` and `translationNs` are both required.', + }) + return z.NEVER + } + } + }) + .transform(({ actorId, operation, data }) => { + const { text, ...links } = data + const trimmedLinks = flush(links) + const id = generateId('internalNote') + const internalNote = Prisma.validator()({ + data: { id, text, ...trimmedLinks }, + select: { id: true }, + }) + const auditLog = Prisma.validator()({ + data: GenerateAuditLog({ + actorId, + operation, + to: { text, ...trimmedLinks }, + internalNoteId: id, + ...trimmedLinks, + }), + select: { id: true }, + }) + return { internalNote, auditLog } + }) + return { dataParser, inputSchema } +} +export type TCreateSchema = z.infer['inputSchema']> diff --git a/packages/api/router/internalNote/query.byId.handler.ts b/packages/api/router/internalNote/query.byId.handler.ts new file mode 100644 index 0000000000..79d96b902f --- /dev/null +++ b/packages/api/router/internalNote/query.byId.handler.ts @@ -0,0 +1,14 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TByIdSchema } from './query.byId.schema' + +export const byId = async ({ input }: TRPCHandlerParams) => { + try { + const result = await prisma.internalNote.findFirstOrThrow({ where: { id: input } }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/internalNote/query.byId.schema.ts b/packages/api/router/internalNote/query.byId.schema.ts new file mode 100644 index 0000000000..828195f60c --- /dev/null +++ b/packages/api/router/internalNote/query.byId.schema.ts @@ -0,0 +1,6 @@ +import { type z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZByIdSchema = prefixedId('internalNote') +export type TByIdSchema = z.infer diff --git a/packages/api/router/internalNote/query.getAllForRecord.handler.ts b/packages/api/router/internalNote/query.getAllForRecord.handler.ts new file mode 100644 index 0000000000..1f14a9093c --- /dev/null +++ b/packages/api/router/internalNote/query.getAllForRecord.handler.ts @@ -0,0 +1,14 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetAllForRecordSchema } from './query.getAllForRecord.schema' + +export const getAllForRecord = async ({ input }: TRPCHandlerParams) => { + try { + const results = prisma.internalNote.findMany({ where: input }) + return results + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/internalNote/query.getAllForRecord.schema.ts b/packages/api/router/internalNote/query.getAllForRecord.schema.ts new file mode 100644 index 0000000000..724c8f39e1 --- /dev/null +++ b/packages/api/router/internalNote/query.getAllForRecord.schema.ts @@ -0,0 +1,41 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +const nonEmptyString = z + .string() + .transform((val) => (String(val).trim() === '' ? undefined : String(val).trim())) + +export const ZGetAllForRecordSchema = z.union([ + z.object({ attributeId: prefixedId('attribute') }), + z.object({ attributeCategoryId: prefixedId('attributeCategory') }), + z.object({ attributeSupplementId: prefixedId('attributeSupplement') }), + z.object({ attributeSupplementDataSchemaId: prefixedId('attributeSupplementDataSchema') }), + z.object({ countryId: prefixedId('country') }), + z.object({ govDistId: prefixedId('govDist') }), + z.object({ govDistTypeId: prefixedId('govDistType') }), + z.object({ languageId: prefixedId('language') }), + z.object({ organizationId: prefixedId('organization') }), + z.object({ orgEmailId: prefixedId('orgEmail') }), + z.object({ orgHoursId: prefixedId('orgHours') }), + z.object({ orgLocationId: prefixedId('orgLocation') }), + z.object({ orgPhoneId: prefixedId('orgPhone') }), + z.object({ orgPhotoId: prefixedId('orgPhoto') }), + z.object({ orgReviewId: prefixedId('orgReview') }), + z.object({ orgServiceId: prefixedId('orgService') }), + z.object({ orgSocialMediaId: prefixedId('orgSocialMedia') }), + z.object({ orgWebsiteId: prefixedId('orgWebsite') }), + z.object({ outsideApiId: prefixedId('outsideAPI') }), + z.object({ permissionId: prefixedId('permission') }), + z.object({ phoneTypeId: prefixedId('phoneType') }), + z.object({ serviceCategoryId: prefixedId('serviceCategory') }), + z.object({ serviceTagId: prefixedId('serviceTag') }), + z.object({ socialMediaLinkId: prefixedId('socialMediaLink') }), + z.object({ socialMediaServiceId: prefixedId('socialMediaService') }), + z.object({ sourceId: prefixedId('source') }), + z.object({ suggestionId: prefixedId('suggestion') }), + z.object({ outsideAPIServiceService: nonEmptyString }), + z.object({ translationKey: nonEmptyString, translationNs: nonEmptyString }), + z.object({ translationNamespaceName: nonEmptyString }), +]) +export type TGetAllForRecordSchema = z.infer diff --git a/packages/api/router/internalNote/schemas.ts b/packages/api/router/internalNote/schemas.ts new file mode 100644 index 0000000000..c078f4e529 --- /dev/null +++ b/packages/api/router/internalNote/schemas.ts @@ -0,0 +1,5 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.create.schema' +export * from './query.byId.schema' +export * from './query.getAllForRecord.schema' +// codegen:end From 9aeca7f8b3ab65fabccc874de269ed33a09403e0 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 17:56:36 -0400 Subject: [PATCH 25/63] omit prisma from context --- packages/api/router/location/selects.ts | 2 +- packages/api/schemas/selects/org.ts | 10 +++++----- packages/api/types/handler.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/api/router/location/selects.ts b/packages/api/router/location/selects.ts index 0a41311f3d..a738865243 100644 --- a/packages/api/router/location/selects.ts +++ b/packages/api/router/location/selects.ts @@ -104,7 +104,7 @@ export const select = { } }, - service(ctx: Context): Prisma.OrgLocationServiceDefaultArgs { + service(ctx: Omit): Prisma.OrgLocationServiceDefaultArgs { return { select: { service: { diff --git a/packages/api/schemas/selects/org.ts b/packages/api/schemas/selects/org.ts index 06944fef15..ea7f6e76a1 100644 --- a/packages/api/schemas/selects/org.ts +++ b/packages/api/schemas/selects/org.ts @@ -182,7 +182,7 @@ const reviewIds = { select: { id: true }, } satisfies Prisma.Organization$reviewsArgs | Prisma.OrgLocation$reviewsArgs | Prisma.OrgService$reviewsArgs -const orgServiceInclude = (ctx: Context) => +const orgServiceInclude = (ctx: Omit) => ({ where: isPublic, select: { @@ -201,7 +201,7 @@ const orgServiceInclude = (ctx: Context) => }, }) satisfies Prisma.Organization$servicesArgs -const orgLocationServiceInclude = (ctx: Context) => +const orgLocationServiceInclude = (ctx: Omit) => ({ where: { service: isPublic, @@ -218,7 +218,7 @@ const photoSelect = { }, } satisfies Prisma.OrgLocation$photosArgs -const userListSelect = (ctx: Context) => { +const userListSelect = (ctx: Omit) => { if (!ctx.session?.user.id) return undefined return { where: { @@ -237,7 +237,7 @@ const userListSelect = (ctx: Context) => { } } -export const orgLocationInclude = (ctx: Context) => +export const orgLocationInclude = (ctx: Omit) => ({ where: isPublic, select: { @@ -266,7 +266,7 @@ export const orgLocationInclude = (ctx: Context) => }, }) satisfies Prisma.Organization$locationsArgs -export const organizationInclude = (ctx: Context) => +export const organizationInclude = (ctx: Omit) => ({ select: { description: freeText, diff --git a/packages/api/types/handler.ts b/packages/api/types/handler.ts index c3df737e08..a82cab4973 100644 --- a/packages/api/types/handler.ts +++ b/packages/api/types/handler.ts @@ -6,7 +6,7 @@ type ContextScenario = 'public' | 'protected' type AuthedContext = SetNonNullable & { actorId: string } export type TRPCHandlerParams = { - ctx: TScenario extends 'public' ? Context : AuthedContext + ctx: TScenario extends 'public' ? Omit : Omit } & (undefined extends TInput ? [TInput] extends [undefined] ? { input?: never } From 85bfadc7e1ed8a779dc900f6c3eef9194d488fbf Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 17:57:06 -0400 Subject: [PATCH 26/63] orgEmail router caching/lazy load --- packages/api/router/orgEmail/index.ts | 269 +++--------------- .../orgEmail/mutation.create.handler.ts | 22 ++ .../router/orgEmail/mutation.create.schema.ts | 52 ++++ .../orgEmail/mutation.update.handler.ts | 32 +++ .../router/orgEmail/mutation.update.schema.ts | 24 ++ .../orgEmail/mutation.upsertMany.handler.ts | 85 ++++++ .../orgEmail/mutation.upsertMany.schema.ts | 23 ++ .../orgEmail/query.forContactInfo.handler.ts | 56 ++++ .../orgEmail/query.forContactInfo.schema.ts | 10 + .../api/router/orgEmail/query.get.handler.ts | 61 ++++ .../api/router/orgEmail/query.get.schema.ts | 31 ++ packages/api/router/orgEmail/schemas.ts | 7 + 12 files changed, 440 insertions(+), 232 deletions(-) create mode 100644 packages/api/router/orgEmail/mutation.create.handler.ts create mode 100644 packages/api/router/orgEmail/mutation.create.schema.ts create mode 100644 packages/api/router/orgEmail/mutation.update.handler.ts create mode 100644 packages/api/router/orgEmail/mutation.update.schema.ts create mode 100644 packages/api/router/orgEmail/mutation.upsertMany.handler.ts create mode 100644 packages/api/router/orgEmail/mutation.upsertMany.schema.ts create mode 100644 packages/api/router/orgEmail/query.forContactInfo.handler.ts create mode 100644 packages/api/router/orgEmail/query.forContactInfo.schema.ts create mode 100644 packages/api/router/orgEmail/query.get.handler.ts create mode 100644 packages/api/router/orgEmail/query.get.schema.ts create mode 100644 packages/api/router/orgEmail/schemas.ts diff --git a/packages/api/router/orgEmail/index.ts b/packages/api/router/orgEmail/index.ts index d0ad16f051..efb0114c8f 100644 --- a/packages/api/router/orgEmail/index.ts +++ b/packages/api/router/orgEmail/index.ts @@ -1,248 +1,53 @@ -import compact from 'just-compact' -import { z } from 'zod' - -import { getIdPrefixRegex, isIdFor, type Prisma } from '@weareinreach/db' -import { generateNestedFreeText } from '@weareinreach/db/lib/generateFreeText' import { defineRouter, permissionedProcedure, publicProcedure } from '~api/lib/trpc' -import { CreateAuditLog } from '~api/schemas/create/auditLog' -import { - CreateOrgEmailSchema, - UpdateOrgEmailSchema, - UpsertManyOrgEmailSchema, -} from '~api/schemas/create/orgEmail' -import { - connectOneId, - connectOrDisconnectId, - createManyOptional, - diffConnectionsMtoN, -} from '~api/schemas/nestedOps' -import { isPublic } from '~api/schemas/selects/common' +import * as schema from './schemas' + +const HandlerCache: Partial = {} +type OrgEmailHandlerCache = { + create: typeof import('./mutation.create.handler').create + update: typeof import('./mutation.update.handler').update + upsertMany: typeof import('./mutation.upsertMany.handler').upsertMany + get: typeof import('./query.get.handler').get + forContactInfo: typeof import('./query.forContactInfo.handler').forContactInfo +} export const orgEmailRouter = defineRouter({ create: permissionedProcedure('createNewEmail') - .input(CreateOrgEmailSchema) + .input(schema.ZCreateSchema) .mutation(async ({ ctx, input }) => { - const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) - const newEmail = await ctx.prisma.orgEmail.create({ - data: { - ...input, - auditLogs, - }, - select: { id: true }, - }) - return newEmail + if (!HandlerCache.create) + HandlerCache.create = await import('./mutation.create.handler').then((mod) => mod.create) + if (!HandlerCache.create) throw new Error('Failed to load handler') + return HandlerCache.create({ ctx, input }) }), update: permissionedProcedure('updateEmail') - .input(UpdateOrgEmailSchema) + .input(schema.ZUpdateSchema) .mutation(async ({ ctx, input }) => { - const { where, data } = input - const updatedRecord = await ctx.prisma.$transaction(async (tx) => { - const current = await tx.orgEmail.findUniqueOrThrow({ where }) - const auditLogs = CreateAuditLog({ - actorId: ctx.session.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const updated = await tx.orgEmail.update({ - where, - data: { - ...data, - auditLogs, - }, - }) - return updated - }) - return updatedRecord + if (!HandlerCache.update) + HandlerCache.update = await import('./mutation.update.handler').then((mod) => mod.update) + if (!HandlerCache.update) throw new Error('Failed to load handler') + return HandlerCache.update({ ctx, input }) }), get: permissionedProcedure('createNewEmail') - .input( - z - .object({ - id: z.string(), - organizationId: z.string(), - orgLocationId: z.string(), - serviceId: z.string(), - }) - .partial() - ) + .input(schema.ZGetSchema) .query(async ({ ctx, input }) => { - const { id, orgLocationId, organizationId, serviceId } = input - - const result = await ctx.prisma.orgEmail.findMany({ - where: { - id, - ...(organizationId ? { organization: { some: { organizationId } } } : {}), - ...(orgLocationId ? { locations: { some: { orgLocationId } } } : {}), - ...(serviceId ? { services: { some: { serviceId } } } : {}), - }, - select: { - id: true, - email: true, - firstName: true, - lastName: true, - titleId: true, - primary: true, - published: true, - deleted: true, - description: { select: { tsKey: { select: { text: true } } } }, - locations: { select: { location: { select: { id: true, name: true } } } }, - organization: { select: { organization: { select: { id: true, name: true, slug: true } } } }, - services: { - select: { - service: { - select: { - id: true, - serviceName: { select: { tsKey: { select: { text: true } } } }, - }, - }, - }, - }, - }, - orderBy: [{ published: 'desc' }, { deleted: 'desc' }], - }) - - const transformedResult = result.map( - ({ description, locations, organization, titleId, services, ...record }) => ({ - ...record, - description: description?.tsKey.text, - locations: locations.map(({ location }) => ({ ...location })), - organization: organization.map(({ organization }) => ({ ...organization })), - title: titleId, - services: services.map(({ service }) => ({ - id: service.id, - serviceName: service.serviceName?.tsKey.text, - })), - }) - ) - return transformedResult + if (!HandlerCache.get) HandlerCache.get = await import('./query.get.handler').then((mod) => mod.get) + if (!HandlerCache.get) throw new Error('Failed to load handler') + return HandlerCache.get({ ctx, input }) }), upsertMany: permissionedProcedure('updateEmail') - .input(UpsertManyOrgEmailSchema) + .input(schema.ZUpsertManySchema) .mutation(async ({ ctx, input }) => { - const { orgId, data } = input - - const existing = await ctx.prisma.orgEmail.findMany({ - where: { - id: { in: compact(data.map(({ id }) => id)) }, - }, - include: { services: true, locations: true }, - }) - const upserts = await ctx.prisma.$transaction( - data.map( - ({ - title, - services: servicesArr, - locations: locationsArr, - description, - id: passedId, - ...record - }) => { - const before = passedId ? existing.find(({ id }) => id === passedId) : undefined - const servicesBefore = before?.services.map(({ serviceId }) => ({ serviceId })) ?? [] - const locationsBefore = before?.locations.map(({ orgLocationId }) => ({ orgLocationId })) ?? [] - const auditLogs = CreateAuditLog({ - actorId: ctx.actorId, - operation: before ? 'UPDATE' : 'CREATE', - from: before, - to: record, - }) - const id = passedId ?? ctx.generateId('orgEmail') - - const services = servicesArr.map((serviceId) => ({ serviceId })) - const locations = locationsArr.map((orgLocationId) => ({ orgLocationId })) - - return ctx.prisma.orgEmail.upsert({ - where: { id }, - create: { - id, - ...record, - title: connectOneId(title), - services: createManyOptional(services), - locations: createManyOptional(locations), - auditLogs, - description: description - ? generateNestedFreeText({ orgId, text: description, type: 'emailDesc', itemId: id }) - : undefined, - }, - update: { - id, - ...record, - title: connectOrDisconnectId(title), - services: diffConnectionsMtoN(services, servicesBefore, 'serviceId'), - locations: diffConnectionsMtoN(locations, locationsBefore, 'orgLocationId'), - description: description - ? { - upsert: { - ...generateNestedFreeText({ - orgId, - text: description, - type: 'emailDesc', - itemId: id, - }), - update: { tsKey: { update: { text: description } } }, - }, - } - : undefined, - auditLogs, - }, - }) - } - ) - ) - return upserts - }), - forContactInfo: publicProcedure - .input( - z.object({ - parentId: z.string().regex(getIdPrefixRegex('organization', 'orgLocation', 'orgService')), - locationOnly: z.boolean().optional(), - serviceOnly: z.boolean().optional(), - }) - ) - .query(async ({ ctx, input }) => { - const whereId = (): Prisma.OrgEmailWhereInput => { - switch (true) { - case isIdFor('organization', input.parentId): { - return { organization: { some: { organization: { id: input.parentId, ...isPublic } } } } - } - case isIdFor('orgLocation', input.parentId): { - return { locations: { some: { location: { id: input.parentId, ...isPublic } } } } - } - case isIdFor('orgService', input.parentId): { - return { services: { some: { service: { id: input.parentId, ...isPublic } } } } - } - default: { - return {} - } - } - } - - const result = await ctx.prisma.orgEmail.findMany({ - where: { - ...isPublic, - ...whereId(), - ...(input.locationOnly !== undefined ? { locationOnly: input.locationOnly } : {}), - ...(input.serviceOnly !== undefined ? { serviceOnly: input.serviceOnly } : {}), - }, - select: { - id: true, - email: true, - primary: true, - title: { select: { key: { select: { key: true } } } }, - description: { select: { tsKey: { select: { text: true, key: true } } } }, - locationOnly: true, - serviceOnly: true, - }, - orderBy: { primary: 'desc' }, - }) - const transformed = result.map(({ description, title, ...record }) => ({ - ...record, - title: title ? { key: title?.key.key } : null, - description: description - ? { key: description?.tsKey.key, defaultText: description?.tsKey.text } - : null, - })) - return transformed + if (!HandlerCache.upsertMany) + HandlerCache.upsertMany = await import('./mutation.upsertMany.handler').then((mod) => mod.upsertMany) + if (!HandlerCache.upsertMany) throw new Error('Failed to load handler') + return HandlerCache.upsertMany({ ctx, input }) }), + forContactInfo: publicProcedure.input(schema.ZForContactInfoSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forContactInfo) + HandlerCache.forContactInfo = await import('./query.forContactInfo.handler').then( + (mod) => mod.forContactInfo + ) + if (!HandlerCache.forContactInfo) throw new Error('Failed to load handler') + return HandlerCache.forContactInfo({ ctx, input }) + }), }) diff --git a/packages/api/router/orgEmail/mutation.create.handler.ts b/packages/api/router/orgEmail/mutation.create.handler.ts new file mode 100644 index 0000000000..d16d945a37 --- /dev/null +++ b/packages/api/router/orgEmail/mutation.create.handler.ts @@ -0,0 +1,22 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateSchema } from './mutation.create.schema' + +export const create = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) + const newEmail = await prisma.orgEmail.create({ + data: { + ...input, + auditLogs, + }, + select: { id: true }, + }) + return newEmail + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgEmail/mutation.create.schema.ts b/packages/api/router/orgEmail/mutation.create.schema.ts new file mode 100644 index 0000000000..70578c6919 --- /dev/null +++ b/packages/api/router/orgEmail/mutation.create.schema.ts @@ -0,0 +1,52 @@ +import { z } from 'zod' + +import { generateId, generateNestedFreeText, Prisma, slug } from '@weareinreach/db' +import { namespace } from '@weareinreach/db/generated/namespaces' + +export const ZCreateSchema = z + .object({ + orgId: z.string(), + data: z.object({ + firstName: z.string().optional(), + lastName: z.string().optional(), + primary: z.boolean().optional(), + email: z.string(), + published: z.boolean().optional(), + locationOnly: z.boolean(), + serviceOnly: z.boolean(), + }), + titleId: z.string().optional(), + title: z.string().optional(), + description: z.string().optional(), + /** + * Associated only with location/service and not overall organization (for large orgs w/ multiple + * locations) + */ + }) + .transform(({ orgId, data, title, titleId, description }) => { + const id = generateId('orgEmail') + + return Prisma.validator()({ + ...data, + description: description + ? generateNestedFreeText({ orgId, itemId: id, text: description, type: 'emailDesc' }) + : undefined, + title: title + ? { + create: { + title, + key: { + create: { + text: title, + key: slug(title), + namespace: { connect: { name: namespace.userTitle } }, + }, + }, + }, + } + : titleId + ? { connect: { id: titleId } } + : undefined, + }) + }) +export type TCreateSchema = z.infer diff --git a/packages/api/router/orgEmail/mutation.update.handler.ts b/packages/api/router/orgEmail/mutation.update.handler.ts new file mode 100644 index 0000000000..e648e59fa6 --- /dev/null +++ b/packages/api/router/orgEmail/mutation.update.handler.ts @@ -0,0 +1,32 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUpdateSchema } from './mutation.update.schema' + +export const update = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgEmail.findUniqueOrThrow({ where }) + const auditLogs = CreateAuditLog({ + actorId: ctx.session.user.id, + operation: 'UPDATE', + from: current, + to: data, + }) + const updated = await tx.orgEmail.update({ + where, + data: { + ...data, + auditLogs, + }, + }) + return updated + }) + return updatedRecord + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgEmail/mutation.update.schema.ts b/packages/api/router/orgEmail/mutation.update.schema.ts new file mode 100644 index 0000000000..d466057c86 --- /dev/null +++ b/packages/api/router/orgEmail/mutation.update.schema.ts @@ -0,0 +1,24 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUpdateSchema = z + .object({ + id: prefixedId('orgEmail'), + data: z + .object({ + firstName: z.string(), + lastName: z.string(), + primary: z.boolean(), + email: z.string(), + published: z.boolean(), + deleted: z.boolean(), + titleId: prefixedId('userTitle'), + locationOnly: z.boolean(), + serviceOnly: z.boolean(), + }) + .partial(), + }) + .transform(({ data, id }) => Prisma.validator()({ where: { id }, data })) +export type TUpdateSchema = z.infer diff --git a/packages/api/router/orgEmail/mutation.upsertMany.handler.ts b/packages/api/router/orgEmail/mutation.upsertMany.handler.ts new file mode 100644 index 0000000000..24647a6d82 --- /dev/null +++ b/packages/api/router/orgEmail/mutation.upsertMany.handler.ts @@ -0,0 +1,85 @@ +import compact from 'just-compact' + +import { generateNestedFreeText, prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { + connectOneId, + connectOrDisconnectId, + createManyOptional, + diffConnectionsMtoN, +} from '~api/schemas/nestedOps' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUpsertManySchema } from './mutation.upsertMany.schema' + +export const upsertMany = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { orgId, data } = input + + const existing = await prisma.orgEmail.findMany({ + where: { + id: { in: compact(data.map(({ id }) => id)) }, + }, + include: { services: true, locations: true }, + }) + const upserts = await prisma.$transaction( + data.map( + ({ title, services: servicesArr, locations: locationsArr, description, id: passedId, ...record }) => { + const before = passedId ? existing.find(({ id }) => id === passedId) : undefined + const servicesBefore = before?.services.map(({ serviceId }) => ({ serviceId })) ?? [] + const locationsBefore = before?.locations.map(({ orgLocationId }) => ({ orgLocationId })) ?? [] + const auditLogs = CreateAuditLog({ + actorId: ctx.actorId, + operation: before ? 'UPDATE' : 'CREATE', + from: before, + to: record, + }) + const id = passedId ?? ctx.generateId('orgEmail') + + const services = servicesArr.map((serviceId) => ({ serviceId })) + const locations = locationsArr.map((orgLocationId) => ({ orgLocationId })) + + return prisma.orgEmail.upsert({ + where: { id }, + create: { + id, + ...record, + title: connectOneId(title), + services: createManyOptional(services), + locations: createManyOptional(locations), + auditLogs, + description: description + ? generateNestedFreeText({ orgId, text: description, type: 'emailDesc', itemId: id }) + : undefined, + }, + update: { + id, + ...record, + title: connectOrDisconnectId(title), + services: diffConnectionsMtoN(services, servicesBefore, 'serviceId'), + locations: diffConnectionsMtoN(locations, locationsBefore, 'orgLocationId'), + description: description + ? { + upsert: { + ...generateNestedFreeText({ + orgId, + text: description, + type: 'emailDesc', + itemId: id, + }), + update: { tsKey: { update: { text: description } } }, + }, + } + : undefined, + auditLogs, + }, + }) + } + ) + ) + return upserts + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgEmail/mutation.upsertMany.schema.ts b/packages/api/router/orgEmail/mutation.upsertMany.schema.ts new file mode 100644 index 0000000000..04e175e9d8 --- /dev/null +++ b/packages/api/router/orgEmail/mutation.upsertMany.schema.ts @@ -0,0 +1,23 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUpsertManySchema = z.object({ + orgId: prefixedId('organization'), + data: z + .object({ + id: prefixedId('orgEmail').optional(), + email: z.string(), + firstName: z.string().nullish(), + lastName: z.string().nullish(), + title: z.string().nullish(), + description: z.string().optional(), + primary: z.boolean(), + published: z.boolean(), + deleted: z.boolean(), + locations: z.string().array(), + services: z.string().array(), + }) + .array(), +}) +export type TUpsertManySchema = z.infer diff --git a/packages/api/router/orgEmail/query.forContactInfo.handler.ts b/packages/api/router/orgEmail/query.forContactInfo.handler.ts new file mode 100644 index 0000000000..f4cc684783 --- /dev/null +++ b/packages/api/router/orgEmail/query.forContactInfo.handler.ts @@ -0,0 +1,56 @@ +import { isIdFor, prisma, type Prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForContactInfoSchema } from './query.forContactInfo.schema' + +export const forContactInfo = async ({ input }: TRPCHandlerParams) => { + try { + const whereId = (): Prisma.OrgEmailWhereInput => { + switch (true) { + case isIdFor('organization', input.parentId): { + return { + organization: { some: { organization: { id: input.parentId, ...globalWhere.isPublic() } } }, + } + } + case isIdFor('orgLocation', input.parentId): { + return { locations: { some: { location: { id: input.parentId, ...globalWhere.isPublic() } } } } + } + case isIdFor('orgService', input.parentId): { + return { services: { some: { service: { id: input.parentId, ...globalWhere.isPublic() } } } } + } + default: { + return {} + } + } + } + + const result = await prisma.orgEmail.findMany({ + where: { + ...globalWhere.isPublic(), + ...whereId(), + ...(input.locationOnly !== undefined ? { locationOnly: input.locationOnly } : {}), + ...(input.serviceOnly !== undefined ? { serviceOnly: input.serviceOnly } : {}), + }, + select: { + id: true, + email: true, + primary: true, + title: { select: { key: { select: { key: true } } } }, + description: { select: { tsKey: { select: { text: true, key: true } } } }, + locationOnly: true, + serviceOnly: true, + }, + orderBy: { primary: 'desc' }, + }) + const transformed = result.map(({ description, title, ...record }) => ({ + ...record, + title: title ? { key: title?.key.key } : null, + description: description ? { key: description?.tsKey.key, defaultText: description?.tsKey.text } : null, + })) + return transformed + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgEmail/query.forContactInfo.schema.ts b/packages/api/router/orgEmail/query.forContactInfo.schema.ts new file mode 100644 index 0000000000..37333e8559 --- /dev/null +++ b/packages/api/router/orgEmail/query.forContactInfo.schema.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForContactInfoSchema = z.object({ + parentId: z.union([prefixedId('organization'), prefixedId('orgLocation'), prefixedId('orgService')]), + locationOnly: z.boolean().optional(), + serviceOnly: z.boolean().optional(), +}) +export type TForContactInfoSchema = z.infer diff --git a/packages/api/router/orgEmail/query.get.handler.ts b/packages/api/router/orgEmail/query.get.handler.ts new file mode 100644 index 0000000000..a1f3961d42 --- /dev/null +++ b/packages/api/router/orgEmail/query.get.handler.ts @@ -0,0 +1,61 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetSchema } from './query.get.schema' + +export const get = async ({ input }: TRPCHandlerParams) => { + try { + const { id, orgLocationId, organizationId, serviceId } = input + + const result = await prisma.orgEmail.findMany({ + where: { + id, + ...(organizationId ? { organization: { some: { organizationId } } } : {}), + ...(orgLocationId ? { locations: { some: { orgLocationId } } } : {}), + ...(serviceId ? { services: { some: { serviceId } } } : {}), + }, + select: { + id: true, + email: true, + firstName: true, + lastName: true, + titleId: true, + primary: true, + published: true, + deleted: true, + description: { select: { tsKey: { select: { text: true } } } }, + locations: { select: { location: { select: { id: true, name: true } } } }, + organization: { select: { organization: { select: { id: true, name: true, slug: true } } } }, + services: { + select: { + service: { + select: { + id: true, + serviceName: { select: { tsKey: { select: { text: true } } } }, + }, + }, + }, + }, + }, + orderBy: [{ published: 'desc' }, { deleted: 'desc' }], + }) + + const transformedResult = result.map( + ({ description, locations, organization, titleId, services, ...record }) => ({ + ...record, + description: description?.tsKey.text, + locations: locations.map(({ location }) => ({ ...location })), + organization: organization.map(({ organization }) => ({ ...organization })), + title: titleId, + services: services.map(({ service }) => ({ + id: service.id, + serviceName: service.serviceName?.tsKey.text, + })), + }) + ) + return transformedResult + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgEmail/query.get.schema.ts b/packages/api/router/orgEmail/query.get.schema.ts new file mode 100644 index 0000000000..7b6f953d88 --- /dev/null +++ b/packages/api/router/orgEmail/query.get.schema.ts @@ -0,0 +1,31 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetSchema = z.union([ + z.object({ + id: prefixedId('orgEmail'), + orgLocationId: z.never(), + serviceId: z.never(), + organizationId: z.never(), + }), + z.object({ + orgLocationId: prefixedId('orgLocation'), + serviceId: z.never(), + organizationId: z.never(), + id: z.never(), + }), + z.object({ + serviceId: prefixedId('orgService'), + organizationId: z.never(), + id: z.never(), + orgLocationId: z.never(), + }), + z.object({ + organizationId: prefixedId('organization'), + id: z.never(), + orgLocationId: z.never(), + serviceId: z.never(), + }), +]) +export type TGetSchema = z.infer diff --git a/packages/api/router/orgEmail/schemas.ts b/packages/api/router/orgEmail/schemas.ts new file mode 100644 index 0000000000..39890d6f7b --- /dev/null +++ b/packages/api/router/orgEmail/schemas.ts @@ -0,0 +1,7 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.create.schema' +export * from './mutation.update.schema' +export * from './mutation.upsertMany.schema' +export * from './query.forContactInfo.schema' +export * from './query.get.schema' +// codegen:end From 1cd1a5fa6e1c41df513a8e24a8e8b1677accb21e Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 18:17:27 -0400 Subject: [PATCH 27/63] orgHours router caching/lazy load --- packages/api/router/orgHours/index.ts | 133 +++++++----------- .../orgHours/mutation.create.handler.ts | 22 +++ .../router/orgHours/mutation.create.schema.ts | 20 +++ .../orgHours/mutation.createMany.handler.ts | 25 ++++ .../orgHours/mutation.createMany.schema.ts | 27 ++++ .../orgHours/mutation.update.handler.ts | 32 +++++ .../router/orgHours/mutation.update.schema.ts | 25 ++++ .../orgHours/query.forHoursDisplay.handler.ts | 38 +++++ .../orgHours/query.forHoursDisplay.schema.ts | 10 ++ .../orgHours/query.forHoursDrawer.handler.ts | 55 ++++++++ .../orgHours/query.forHoursDrawer.schema.ts | 10 ++ .../router/orgHours/query.getTz.handler.ts | 19 +++ .../api/router/orgHours/query.getTz.schema.ts | 4 + packages/api/router/orgHours/schemas.ts | 8 ++ 14 files changed, 344 insertions(+), 84 deletions(-) create mode 100644 packages/api/router/orgHours/mutation.create.handler.ts create mode 100644 packages/api/router/orgHours/mutation.create.schema.ts create mode 100644 packages/api/router/orgHours/mutation.createMany.handler.ts create mode 100644 packages/api/router/orgHours/mutation.createMany.schema.ts create mode 100644 packages/api/router/orgHours/mutation.update.handler.ts create mode 100644 packages/api/router/orgHours/mutation.update.schema.ts create mode 100644 packages/api/router/orgHours/query.forHoursDisplay.handler.ts create mode 100644 packages/api/router/orgHours/query.forHoursDisplay.schema.ts create mode 100644 packages/api/router/orgHours/query.forHoursDrawer.handler.ts create mode 100644 packages/api/router/orgHours/query.forHoursDrawer.schema.ts create mode 100644 packages/api/router/orgHours/query.getTz.handler.ts create mode 100644 packages/api/router/orgHours/query.getTz.schema.ts create mode 100644 packages/api/router/orgHours/schemas.ts diff --git a/packages/api/router/orgHours/index.ts b/packages/api/router/orgHours/index.ts index 3e2c75cd7c..4c3108cef4 100644 --- a/packages/api/router/orgHours/index.ts +++ b/packages/api/router/orgHours/index.ts @@ -1,100 +1,65 @@ -import { TRPCError } from '@trpc/server' -import { z } from 'zod' - -import { getIdPrefixRegex, isIdFor, type Prisma } from '@weareinreach/db' -import { getTz } from '~api/lib/getTz' import { defineRouter, permissionedProcedure, publicProcedure, staffProcedure } from '~api/lib/trpc' -import { coord } from '~api/schemas/common' -import { CreateAuditLog } from '~api/schemas/create/auditLog' -import { CreateManyOrgHours, CreateOrgHoursSchema, UpdateOrgHoursSchema } from '~api/schemas/create/orgHours' -import { isPublic } from '~api/schemas/selects/common' + +import * as schema from './schemas' + +const HandlerCache: Partial = {} +type OrgHoursHandlerCache = { + create: typeof import('./mutation.create.handler').create + update: typeof import('./mutation.update.handler').update + createMany: typeof import('./mutation.createMany.handler').createMany + getTz: typeof import('./query.getTz.handler').getTz + forHoursDisplay: typeof import('./query.forHoursDisplay.handler').forHoursDisplay + forHoursDrawer: typeof import('./query.forHoursDrawer.handler').forHoursDrawer +} export const orgHoursRouter = defineRouter({ create: permissionedProcedure('createNewHours') - .input(CreateOrgHoursSchema) + .input(schema.ZCreateSchema) .mutation(async ({ ctx, input }) => { - const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) - const newRecord = await ctx.prisma.orgHours.create({ - data: { - ...input, - auditLogs, - }, - select: { id: true }, - }) - return newRecord + if (!HandlerCache.create) + HandlerCache.create = await import('./mutation.create.handler').then((mod) => mod.create) + if (!HandlerCache.create) throw new Error('Failed to load handler') + return HandlerCache.create({ ctx, input }) }), createMany: permissionedProcedure('createNewHours') - .input(CreateManyOrgHours().inputSchema) + .input(schema.ZCreateManySchema().inputSchema) .mutation(async ({ ctx, input }) => { - const inputData = { - actorId: ctx.session.user.id, - operation: 'CREATE', - data: input, - } - const { orgHours, auditLogs } = CreateManyOrgHours().dataParser.parse(inputData) - const results = await ctx.prisma.$transaction(async (tx) => { - const hours = await tx.orgHours.createMany(orgHours) - const logs = await tx.auditLog.createMany(auditLogs) - - return { orgHours: hours.count, auditLogs: logs.count, balanced: hours.count === logs.count } - }) - return results + if (!HandlerCache.createMany) + HandlerCache.createMany = await import('./mutation.createMany.handler').then((mod) => mod.createMany) + if (!HandlerCache.createMany) throw new Error('Failed to load handler') + return HandlerCache.createMany({ ctx, input }) }), update: permissionedProcedure('updateHours') - .input(UpdateOrgHoursSchema) + .input(schema.ZUpdateSchema) .mutation(async ({ input, ctx }) => { - const { where, data } = input - const updatedRecord = await ctx.prisma.$transaction(async (tx) => { - const current = await tx.orgHours.findUniqueOrThrow({ where }) - const auditLogs = CreateAuditLog({ - actorId: ctx.session.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const updated = await tx.orgHours.update({ - where, - data: { - ...data, - auditLogs, - }, - }) - return updated - }) - return updatedRecord + if (!HandlerCache.update) + HandlerCache.update = await import('./mutation.update.handler').then((mod) => mod.update) + if (!HandlerCache.update) throw new Error('Failed to load handler') + return HandlerCache.update({ ctx, input }) }), - getTz: staffProcedure.input(coord).query(({ input }) => { - const result = getTz(input) - if (!result) throw new TRPCError({ code: 'NOT_FOUND' }) - return result + getTz: staffProcedure.input(schema.ZGetTzSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getTz) + HandlerCache.getTz = await import('./query.getTz.handler').then((mod) => mod.getTz) + if (!HandlerCache.getTz) throw new Error('Failed to load handler') + return HandlerCache.getTz({ ctx, input }) + }), + forHoursDisplay: publicProcedure.input(schema.ZForHoursDisplaySchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forHoursDisplay) + HandlerCache.forHoursDisplay = await import('./query.forHoursDisplay.handler').then( + (mod) => mod.forHoursDisplay + ) + if (!HandlerCache.forHoursDisplay) throw new Error('Failed to load handler') + return HandlerCache.forHoursDisplay({ ctx, input }) }), - forHoursDisplay: publicProcedure - .input(z.string().regex(getIdPrefixRegex('organization', 'orgLocation', 'orgService'))) + forHoursDrawer: publicProcedure + // permissionedProcedure('updateHours') + .input(schema.ZForHoursDrawerSchema) .query(async ({ ctx, input }) => { - const whereId = (): Prisma.OrgHoursWhereInput => { - switch (true) { - case isIdFor('organization', input): { - return { organization: { id: input, ...isPublic } } - } - case isIdFor('orgLocation', input): { - return { orgLocation: { id: input, ...isPublic } } - } - case isIdFor('orgService', input): { - return { orgService: { id: input, ...isPublic } } - } - default: { - return {} - } - } - } - - const result = await ctx.prisma.orgHours.findMany({ - where: { - ...whereId(), - }, - select: { id: true, dayIndex: true, start: true, end: true, closed: true, tz: true }, - orderBy: [{ dayIndex: 'asc' }, { start: 'asc' }], - }) - return result + if (!HandlerCache.forHoursDrawer) + HandlerCache.forHoursDrawer = await import('./query.forHoursDrawer.handler').then( + (mod) => mod.forHoursDrawer + ) + if (!HandlerCache.forHoursDrawer) throw new Error('Failed to load handler') + return HandlerCache.forHoursDrawer({ ctx, input }) }), }) diff --git a/packages/api/router/orgHours/mutation.create.handler.ts b/packages/api/router/orgHours/mutation.create.handler.ts new file mode 100644 index 0000000000..cfcb8ed88f --- /dev/null +++ b/packages/api/router/orgHours/mutation.create.handler.ts @@ -0,0 +1,22 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateSchema } from './mutation.create.schema' + +export const create = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) + const newRecord = await prisma.orgHours.create({ + data: { + ...input, + auditLogs, + }, + select: { id: true }, + }) + return newRecord + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgHours/mutation.create.schema.ts b/packages/api/router/orgHours/mutation.create.schema.ts new file mode 100644 index 0000000000..8f92b993c5 --- /dev/null +++ b/packages/api/router/orgHours/mutation.create.schema.ts @@ -0,0 +1,20 @@ +import { DateTime } from 'luxon' +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZCreateSchema = z.object({ + /** Sun 0, Mon 1, Tue 2, Wed 3, Thu 3, Fri 4, Sat 6 */ + dayIndex: z.number().gte(0).lte(6), + start: z.coerce.date(), + end: z.coerce.date(), + tz: z + .string() + .refine((val) => DateTime.now().setZone(val).isValid) + .optional(), + orgLocId: prefixedId('orgLocation').optional(), + orgServiceId: prefixedId('orgService').optional(), + organizationId: prefixedId('organization').optional(), + closed: z.boolean(), +}) +export type TCreateSchema = z.infer diff --git a/packages/api/router/orgHours/mutation.createMany.handler.ts b/packages/api/router/orgHours/mutation.createMany.handler.ts new file mode 100644 index 0000000000..6016bbaa23 --- /dev/null +++ b/packages/api/router/orgHours/mutation.createMany.handler.ts @@ -0,0 +1,25 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateManySchema, ZCreateManySchema } from './mutation.createMany.schema' + +export const createMany = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const inputData = { + actorId: ctx.session.user.id, + operation: 'CREATE', + data: input, + } + const { orgHours, auditLogs } = ZCreateManySchema().dataParser.parse(inputData) + const results = await prisma.$transaction(async (tx) => { + const hours = await tx.orgHours.createMany(orgHours) + const logs = await tx.auditLog.createMany(auditLogs) + + return { orgHours: hours.count, auditLogs: logs.count, balanced: hours.count === logs.count } + }) + return results + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgHours/mutation.createMany.schema.ts b/packages/api/router/orgHours/mutation.createMany.schema.ts new file mode 100644 index 0000000000..c54f762ed6 --- /dev/null +++ b/packages/api/router/orgHours/mutation.createMany.schema.ts @@ -0,0 +1,27 @@ +import { type z } from 'zod' + +import { generateId, type Prisma } from '@weareinreach/db' +import { CreateManyBase } from '~api/schemaBase/createMany' +import { GenerateAuditLog } from '~api/schemas/create/auditLog' + +import { ZCreateSchema } from './mutation.create.schema' + +export const ZCreateManySchema = () => { + const { dataParser: parser, inputSchema } = CreateManyBase(ZCreateSchema.array()) + const dataParser = parser.transform(({ actorId, operation, data: parsedData }) => { + const orgHours: Prisma.OrgHoursCreateManyInput[] = [] + const auditLogs: Prisma.AuditLogCreateManyInput[] = [] + for (const item of parsedData) { + const id = generateId('orgHours') + const audit = GenerateAuditLog({ actorId, operation, to: item, orgHoursId: id }) + orgHours.push({ id, ...item }) + auditLogs.push(audit) + } + return { + orgHours: { data: orgHours, skipDuplicates: true }, + auditLogs: { data: auditLogs, skipDuplicates: true }, + } + }) + return { dataParser, inputSchema } +} +export type TCreateManySchema = z.infer['inputSchema']> diff --git a/packages/api/router/orgHours/mutation.update.handler.ts b/packages/api/router/orgHours/mutation.update.handler.ts new file mode 100644 index 0000000000..d3698cf0df --- /dev/null +++ b/packages/api/router/orgHours/mutation.update.handler.ts @@ -0,0 +1,32 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUpdateSchema } from './mutation.update.schema' + +export const update = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgHours.findUniqueOrThrow({ where }) + const auditLogs = CreateAuditLog({ + actorId: ctx.session.user.id, + operation: 'UPDATE', + from: current, + to: data, + }) + const updated = await tx.orgHours.update({ + where, + data: { + ...data, + auditLogs, + }, + }) + return updated + }) + return updatedRecord + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgHours/mutation.update.schema.ts b/packages/api/router/orgHours/mutation.update.schema.ts new file mode 100644 index 0000000000..b6820524ef --- /dev/null +++ b/packages/api/router/orgHours/mutation.update.schema.ts @@ -0,0 +1,25 @@ +import { DateTime } from 'luxon' +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUpdateSchema = z + .object({ + id: prefixedId('orgHours'), + data: z + .object({ + /** Sun 0, Mon 1, Tue 2, Wed 3, Thu 3, Fri 4, Sat 6 */ + dayIndex: z.number().gte(0).lte(6), + start: z.coerce.date(), + end: z.coerce.date(), + tz: z.string().refine((val) => DateTime.now().setZone(val).isValid), + orgLocId: prefixedId('orgLocation'), + orgServiceId: prefixedId('orgService'), + organizationId: prefixedId('organization'), + closed: z.boolean(), + }) + .partial(), + }) + .transform(({ data, id }) => Prisma.validator()({ where: { id }, data })) +export type TUpdateSchema = z.infer diff --git a/packages/api/router/orgHours/query.forHoursDisplay.handler.ts b/packages/api/router/orgHours/query.forHoursDisplay.handler.ts new file mode 100644 index 0000000000..b1d143428d --- /dev/null +++ b/packages/api/router/orgHours/query.forHoursDisplay.handler.ts @@ -0,0 +1,38 @@ +import { isIdFor, prisma, type Prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForHoursDisplaySchema } from './query.forHoursDisplay.schema' + +export const forHoursDisplay = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const whereId = (): Prisma.OrgHoursWhereInput => { + switch (true) { + case isIdFor('organization', input): { + return { organization: { id: input, ...globalWhere.isPublic() } } + } + case isIdFor('orgLocation', input): { + return { orgLocation: { id: input, ...globalWhere.isPublic() } } + } + case isIdFor('orgService', input): { + return { orgService: { id: input, ...globalWhere.isPublic() } } + } + default: { + return {} + } + } + } + + const result = await prisma.orgHours.findMany({ + where: { + ...whereId(), + }, + select: { id: true, dayIndex: true, start: true, end: true, closed: true, tz: true }, + orderBy: [{ dayIndex: 'asc' }, { start: 'asc' }], + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgHours/query.forHoursDisplay.schema.ts b/packages/api/router/orgHours/query.forHoursDisplay.schema.ts new file mode 100644 index 0000000000..465abb4e36 --- /dev/null +++ b/packages/api/router/orgHours/query.forHoursDisplay.schema.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForHoursDisplaySchema = z.union([ + prefixedId('organization'), + prefixedId('orgService'), + prefixedId('orgLocation'), +]) +export type TForHoursDisplaySchema = z.infer diff --git a/packages/api/router/orgHours/query.forHoursDrawer.handler.ts b/packages/api/router/orgHours/query.forHoursDrawer.handler.ts new file mode 100644 index 0000000000..8cb8bee7e8 --- /dev/null +++ b/packages/api/router/orgHours/query.forHoursDrawer.handler.ts @@ -0,0 +1,55 @@ +import { DateTime } from 'luxon' + +import { isIdFor, prisma, type Prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForHoursDrawerSchema } from './query.forHoursDrawer.schema' + +export const forHoursDrawer = async ({ input }: TRPCHandlerParams) => { + try { + const whereId = (): Prisma.OrgHoursWhereInput => { + switch (true) { + case isIdFor('organization', input): { + return { organization: { id: input, ...globalWhere.isPublic() } } + } + case isIdFor('orgLocation', input): { + return { orgLocation: { id: input, ...globalWhere.isPublic() } } + } + case isIdFor('orgService', input): { + return { orgService: { id: input, ...globalWhere.isPublic() } } + } + default: { + return {} + } + } + } + + const result = await prisma.orgHours.findMany({ + where: { + ...whereId(), + }, + select: { id: true, dayIndex: true, start: true, end: true, closed: true, tz: true }, + orderBy: [{ dayIndex: 'asc' }, { start: 'asc' }], + }) + const transformedResult = result.map(({ start, end, ...rest }) => { + return { + start: DateTime.fromJSDate(start, { zone: rest.tz ?? 'America/New_York' }).toISOTime({ + suppressMilliseconds: true, + suppressSeconds: true, + includeOffset: false, + }), + end: DateTime.fromJSDate(end, { zone: rest.tz ?? 'America/New_York' }).toISOTime({ + suppressMilliseconds: true, + suppressSeconds: true, + includeOffset: false, + }), + ...rest, + } + }) + return transformedResult + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgHours/query.forHoursDrawer.schema.ts b/packages/api/router/orgHours/query.forHoursDrawer.schema.ts new file mode 100644 index 0000000000..bd9f0679cb --- /dev/null +++ b/packages/api/router/orgHours/query.forHoursDrawer.schema.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForHoursDrawerSchema = z.union([ + prefixedId('organization'), + prefixedId('orgService'), + prefixedId('orgLocation'), +]) +export type TForHoursDrawerSchema = z.infer diff --git a/packages/api/router/orgHours/query.getTz.handler.ts b/packages/api/router/orgHours/query.getTz.handler.ts new file mode 100644 index 0000000000..f6cb6917d4 --- /dev/null +++ b/packages/api/router/orgHours/query.getTz.handler.ts @@ -0,0 +1,19 @@ +import { TRPCError } from '@trpc/server' +import { find } from 'geo-tz' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetTzSchema } from './query.getTz.schema' + +export const getTz = async ({ input }: TRPCHandlerParams) => { + try { + const { lat, lon } = input + const result = find(lat, lon).at(0) + if (!result) throw new TRPCError({ code: 'NOT_FOUND' }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgHours/query.getTz.schema.ts b/packages/api/router/orgHours/query.getTz.schema.ts new file mode 100644 index 0000000000..69b12417a8 --- /dev/null +++ b/packages/api/router/orgHours/query.getTz.schema.ts @@ -0,0 +1,4 @@ +import { z } from 'zod' + +export const ZGetTzSchema = z.object({ lat: z.number().gte(-90).lte(90), lon: z.number().gte(-180).lte(180) }) +export type TGetTzSchema = z.infer diff --git a/packages/api/router/orgHours/schemas.ts b/packages/api/router/orgHours/schemas.ts new file mode 100644 index 0000000000..8c022e86d5 --- /dev/null +++ b/packages/api/router/orgHours/schemas.ts @@ -0,0 +1,8 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.create.schema' +export * from './mutation.createMany.schema' +export * from './mutation.update.schema' +export * from './query.forHoursDisplay.schema' +export * from './query.forHoursDrawer.schema' +export * from './query.getTz.schema' +// codegen:end From 878cf07bf12d8c16a1f619f3f122d2858ff16021 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 18:44:20 -0400 Subject: [PATCH 28/63] orgPhone router caching/lazy load --- packages/api/router/orgPhone/index.ts | 273 +++--------------- .../orgPhone/mutation.create.handler.ts | 22 ++ .../router/orgPhone/mutation.create.schema.ts | 57 ++++ .../orgPhone/mutation.update.handler.ts | 32 ++ .../router/orgPhone/mutation.update.schema.ts | 23 ++ .../orgPhone/mutation.upsertMany.handler.ts | 96 ++++++ .../orgPhone/mutation.upsertMany.schema.ts | 24 ++ .../orgPhone/query.forContactInfo.handler.ts | 55 ++++ .../orgPhone/query.forContactInfo.schema.ts | 9 + .../api/router/orgPhone/query.get.handler.ts | 61 ++++ .../api/router/orgPhone/query.get.schema.ts | 31 ++ packages/api/router/orgPhone/schemas.ts | 7 + 12 files changed, 454 insertions(+), 236 deletions(-) create mode 100644 packages/api/router/orgPhone/mutation.create.handler.ts create mode 100644 packages/api/router/orgPhone/mutation.create.schema.ts create mode 100644 packages/api/router/orgPhone/mutation.update.handler.ts create mode 100644 packages/api/router/orgPhone/mutation.update.schema.ts create mode 100644 packages/api/router/orgPhone/mutation.upsertMany.handler.ts create mode 100644 packages/api/router/orgPhone/mutation.upsertMany.schema.ts create mode 100644 packages/api/router/orgPhone/query.forContactInfo.handler.ts create mode 100644 packages/api/router/orgPhone/query.forContactInfo.schema.ts create mode 100644 packages/api/router/orgPhone/query.get.handler.ts create mode 100644 packages/api/router/orgPhone/query.get.schema.ts create mode 100644 packages/api/router/orgPhone/schemas.ts diff --git a/packages/api/router/orgPhone/index.ts b/packages/api/router/orgPhone/index.ts index c5e4b9a00e..15f8542360 100644 --- a/packages/api/router/orgPhone/index.ts +++ b/packages/api/router/orgPhone/index.ts @@ -1,252 +1,53 @@ -import compact from 'just-compact' -import { z } from 'zod' - -import { getIdPrefixRegex, isIdFor, type Prisma } from '@weareinreach/db' -import { generateNestedFreeText } from '@weareinreach/db/lib/generateFreeText' import { defineRouter, permissionedProcedure, publicProcedure } from '~api/lib/trpc' -import { CreateAuditLog } from '~api/schemas/create/auditLog' -import { - CreateOrgPhoneSchema, - UpdateOrgPhoneSchema, - UpsertManyOrgPhoneSchema, -} from '~api/schemas/create/orgPhone' -import { - connectOneId, - connectOneIdRequired, - connectOrDisconnectId, - createManyOptional, - diffConnectionsMtoN, -} from '~api/schemas/nestedOps' -import { isPublic } from '~api/schemas/selects/common' +import * as schema from './schemas' + +const HandlerCache: Partial = {} +type OrgPhoneHandlerCache = { + create: typeof import('./mutation.create.handler').create + update: typeof import('./mutation.update.handler').update + upsertMany: typeof import('./mutation.upsertMany.handler').upsertMany + get: typeof import('./query.get.handler').get + forContactInfo: typeof import('./query.forContactInfo.handler').forContactInfo +} export const orgPhoneRouter = defineRouter({ create: permissionedProcedure('createNewPhone') - .input(CreateOrgPhoneSchema) + .input(schema.ZCreateSchema) .mutation(async ({ ctx, input }) => { - const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) - const newPhone = await ctx.prisma.orgPhone.create({ - data: { - ...input, - auditLogs, - }, - select: { id: true }, - }) - return newPhone + if (!HandlerCache.create) + HandlerCache.create = await import('./mutation.create.handler').then((mod) => mod.create) + if (!HandlerCache.create) throw new Error('Failed to load handler') + return HandlerCache.create({ ctx, input }) }), update: permissionedProcedure('updatePhone') - .input(UpdateOrgPhoneSchema) + .input(schema.ZUpdateSchema) .mutation(async ({ input, ctx }) => { - const { where, data } = input - const updatedRecord = await ctx.prisma.$transaction(async (tx) => { - const current = await tx.orgPhone.findUniqueOrThrow({ where }) - const auditLogs = CreateAuditLog({ - actorId: ctx.session.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const updated = await tx.orgPhone.update({ - where, - data: { - ...data, - auditLogs, - }, - }) - return updated - }) - return updatedRecord + if (!HandlerCache.update) + HandlerCache.update = await import('./mutation.update.handler').then((mod) => mod.update) + if (!HandlerCache.update) throw new Error('Failed to load handler') + return HandlerCache.update({ ctx, input }) }), get: permissionedProcedure('createNewPhone') - .input( - z - .object({ - id: z.string(), - organizationId: z.string(), - orgLocationId: z.string(), - serviceId: z.string(), - }) - .partial() - ) + .input(schema.ZGetSchema) .query(async ({ ctx, input }) => { - const { id, orgLocationId, organizationId, serviceId } = input - - const result = await ctx.prisma.orgPhone.findMany({ - where: { - id, - ...(organizationId ? { organization: { organizationId } } : {}), - ...(orgLocationId ? { locations: { some: { orgLocationId } } } : {}), - ...(serviceId ? { services: { some: { serviceId } } } : {}), - }, - select: { - id: true, - number: true, - ext: true, - deleted: true, - primary: true, - published: true, - country: { select: { cca2: true, id: true } }, - description: { select: { tsKey: { select: { text: true } } } }, - locations: { select: { location: { select: { id: true, name: true } } } }, - organization: { select: { organization: { select: { id: true, name: true, slug: true } } } }, - phoneType: { select: { id: true, type: true, tsKey: true, tsNs: true } }, - services: { - select: { - service: { - select: { - id: true, - serviceName: { select: { tsKey: { select: { text: true } } } }, - }, - }, - }, - }, - }, - orderBy: [{ published: 'desc' }, { deleted: 'desc' }], - }) - - const transformedResult = result.map( - ({ description, locations, organization, phoneType, services, ...record }) => ({ - ...record, - description: description?.tsKey.text, - locations: locations?.map(({ location }) => ({ ...location })), - organization: { ...organization?.organization }, - phoneType, - services: services?.map(({ service }) => ({ - id: service.id, - serviceName: service.serviceName?.tsKey.text, - })), - }) - ) - return transformedResult + if (!HandlerCache.get) HandlerCache.get = await import('./query.get.handler').then((mod) => mod.get) + if (!HandlerCache.get) throw new Error('Failed to load handler') + return HandlerCache.get({ ctx, input }) }), upsertMany: permissionedProcedure('updatePhone') - .input(UpsertManyOrgPhoneSchema) + .input(schema.ZUpsertManySchema) .mutation(async ({ ctx, input }) => { - const { orgId, data } = input - - const existing = await ctx.prisma.orgPhone.findMany({ - where: { - id: { in: compact(data.map(({ id }) => id)) }, - }, - include: { services: true, locations: true }, - }) - const upserts = await ctx.prisma.$transaction( - data.map( - ({ - phoneType, - country, - services: servicesArr, - locations: locationsArr, - description, - id: passedId, - ...record - }) => { - const before = passedId ? existing.find(({ id }) => id === passedId) : undefined - const servicesBefore = before?.services.map(({ serviceId }) => ({ serviceId })) ?? [] - const locationsBefore = before?.locations.map(({ orgLocationId }) => ({ orgLocationId })) ?? [] - const auditLogs = CreateAuditLog({ - actorId: ctx.actorId, - operation: before ? 'UPDATE' : 'CREATE', - from: before, - to: record, - }) - const id = passedId ?? ctx.generateId('orgPhone') - - const services = servicesArr.map((serviceId) => ({ serviceId })) - const locations = locationsArr.map((orgLocationId) => ({ orgLocationId })) - - return ctx.prisma.orgPhone.upsert({ - where: { id }, - create: { - id, - ...record, - country: connectOneIdRequired(country.id), - phoneType: connectOneId(phoneType), - services: createManyOptional(services), - locations: createManyOptional(locations), - auditLogs, - description: description - ? generateNestedFreeText({ orgId, text: description, type: 'phoneDesc', itemId: id }) - : undefined, - }, - update: { - id, - ...record, - country: connectOneIdRequired(country.id), - phoneType: connectOrDisconnectId(phoneType), - services: diffConnectionsMtoN(services, servicesBefore, 'serviceId'), - locations: diffConnectionsMtoN(locations, locationsBefore, 'orgLocationId'), - description: description - ? { - upsert: { - ...generateNestedFreeText({ - orgId, - text: description, - type: 'phoneDesc', - itemId: id, - }), - update: { tsKey: { update: { text: description } } }, - }, - } - : undefined, - auditLogs, - }, - }) - } - ) - ) - return upserts - }), - forContactInfo: publicProcedure - .input( - z.object({ - parentId: z.string().regex(getIdPrefixRegex('organization', 'orgLocation', 'orgService')), - locationOnly: z.boolean().optional(), - }) - ) - .query(async ({ ctx, input }) => { - const whereId = (): Prisma.OrgPhoneWhereInput => { - switch (true) { - case isIdFor('organization', input.parentId): { - return { organization: { organization: { id: input.parentId, ...isPublic } } } - } - case isIdFor('orgLocation', input.parentId): { - return { locations: { some: { location: { id: input.parentId, ...isPublic } } } } - } - case isIdFor('orgService', input.parentId): { - return { services: { some: { service: { id: input.parentId, ...isPublic } } } } - } - default: { - return {} - } - } - } - - const result = await ctx.prisma.orgPhone.findMany({ - where: { - ...isPublic, - ...whereId(), - ...(input.locationOnly !== undefined ? { locationOnly: input.locationOnly } : {}), - }, - select: { - id: true, - number: true, - ext: true, - country: { select: { cca2: true } }, - primary: true, - description: { select: { tsKey: { select: { text: true, key: true } } } }, - phoneType: { select: { key: { select: { text: true, key: true } } } }, - locationOnly: true, - }, - orderBy: { primary: 'desc' }, - }) - const transformed = result.map(({ description, phoneType, country, ...record }) => ({ - ...record, - country: country?.cca2, - phoneType: phoneType ? { key: phoneType?.key.key, defaultText: phoneType?.key.text } : null, - description: description - ? { key: description?.tsKey.key, defaultText: description?.tsKey.text } - : null, - })) - return transformed + if (!HandlerCache.upsertMany) + HandlerCache.upsertMany = await import('./mutation.upsertMany.handler').then((mod) => mod.upsertMany) + if (!HandlerCache.upsertMany) throw new Error('Failed to load handler') + return HandlerCache.upsertMany({ ctx, input }) }), + forContactInfo: publicProcedure.input(schema.ZForContactInfoSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forContactInfo) + HandlerCache.forContactInfo = await import('./query.forContactInfo.handler').then( + (mod) => mod.forContactInfo + ) + if (!HandlerCache.forContactInfo) throw new Error('Failed to load handler') + return HandlerCache.forContactInfo({ ctx, input }) + }), }) diff --git a/packages/api/router/orgPhone/mutation.create.handler.ts b/packages/api/router/orgPhone/mutation.create.handler.ts new file mode 100644 index 0000000000..9e1730c377 --- /dev/null +++ b/packages/api/router/orgPhone/mutation.create.handler.ts @@ -0,0 +1,22 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateSchema } from './mutation.create.schema' + +export const create = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) + const newPhone = await prisma.orgPhone.create({ + data: { + ...input, + auditLogs, + }, + select: { id: true }, + }) + return newPhone + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgPhone/mutation.create.schema.ts b/packages/api/router/orgPhone/mutation.create.schema.ts new file mode 100644 index 0000000000..3b07e0d3bd --- /dev/null +++ b/packages/api/router/orgPhone/mutation.create.schema.ts @@ -0,0 +1,57 @@ +import { z } from 'zod' + +import { generateId, generateNestedFreeText, Prisma, slug } from '@weareinreach/db' +import { namespace } from '@weareinreach/db/generated/namespaces' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZCreateSchema = z + .object({ + orgId: prefixedId('organization'), + data: z.object({ + number: z.string(), + ext: z.string().optional(), + primary: z.boolean().optional(), + published: z.boolean().optional(), + countryId: prefixedId('country'), + phoneTypeId: prefixedId('phoneType').optional(), + phoneTypeNew: z.string().optional(), + description: z.string().optional(), + locationOnly: z.boolean().optional(), + }), + }) + .transform(({ data, orgId }) => { + const id = generateId('orgPhone') + const description = data.description + ? generateNestedFreeText({ orgId, itemId: id, text: data.description, type: 'phoneDesc' }) + : undefined + const phoneType = data.phoneTypeId + ? { connect: { id: data.phoneTypeId } } + : data.phoneTypeNew + ? { + create: { + type: data.phoneTypeNew, + key: { + create: { + key: slug(data.phoneTypeNew), + text: data.phoneTypeNew, + namespace: { connect: { name: namespace.phoneType } }, + }, + }, + }, + } + : undefined + + const { number, ext, locationOnly, primary, published } = data + return Prisma.validator()({ + id, + number, + ext, + locationOnly, + primary, + published, + country: { connect: { id: data.countryId } }, + description, + phoneType, + }) + }) +export type TCreateSchema = z.infer diff --git a/packages/api/router/orgPhone/mutation.update.handler.ts b/packages/api/router/orgPhone/mutation.update.handler.ts new file mode 100644 index 0000000000..df8f0d9976 --- /dev/null +++ b/packages/api/router/orgPhone/mutation.update.handler.ts @@ -0,0 +1,32 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUpdateSchema } from './mutation.update.schema' + +export const update = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgPhone.findUniqueOrThrow({ where }) + const auditLogs = CreateAuditLog({ + actorId: ctx.session.user.id, + operation: 'UPDATE', + from: current, + to: data, + }) + const updated = await tx.orgPhone.update({ + where, + data: { + ...data, + auditLogs, + }, + }) + return updated + }) + return updatedRecord + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgPhone/mutation.update.schema.ts b/packages/api/router/orgPhone/mutation.update.schema.ts new file mode 100644 index 0000000000..42090693c9 --- /dev/null +++ b/packages/api/router/orgPhone/mutation.update.schema.ts @@ -0,0 +1,23 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUpdateSchema = z + .object({ + id: prefixedId('orgPhone'), + data: z + .object({ + number: z.string(), + ext: z.string(), + primary: z.boolean(), + published: z.boolean(), + deleted: z.boolean(), + countryId: prefixedId('country'), + phoneTypeId: prefixedId('phoneType'), + locationOnly: z.boolean(), + }) + .partial(), + }) + .transform(({ data, id }) => Prisma.validator()({ where: { id }, data })) +export type TUpdateSchema = z.infer diff --git a/packages/api/router/orgPhone/mutation.upsertMany.handler.ts b/packages/api/router/orgPhone/mutation.upsertMany.handler.ts new file mode 100644 index 0000000000..31385a2a68 --- /dev/null +++ b/packages/api/router/orgPhone/mutation.upsertMany.handler.ts @@ -0,0 +1,96 @@ +import compact from 'just-compact' + +import { generateNestedFreeText, prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { + connectOneId, + connectOneIdRequired, + connectOrDisconnectId, + createManyOptional, + diffConnectionsMtoN, +} from '~api/schemas/nestedOps' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUpsertManySchema } from './mutation.upsertMany.schema' + +export const upsertMany = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { orgId, data } = input + + const existing = await prisma.orgPhone.findMany({ + where: { + id: { in: compact(data.map(({ id }) => id)) }, + }, + include: { services: true, locations: true }, + }) + const upserts = await prisma.$transaction( + data.map( + ({ + phoneType, + country, + services: servicesArr, + locations: locationsArr, + description, + id: passedId, + ...record + }) => { + const before = passedId ? existing.find(({ id }) => id === passedId) : undefined + const servicesBefore = before?.services.map(({ serviceId }) => ({ serviceId })) ?? [] + const locationsBefore = before?.locations.map(({ orgLocationId }) => ({ orgLocationId })) ?? [] + const auditLogs = CreateAuditLog({ + actorId: ctx.actorId, + operation: before ? 'UPDATE' : 'CREATE', + from: before, + to: record, + }) + const id = passedId ?? ctx.generateId('orgPhone') + + const services = servicesArr.map((serviceId) => ({ serviceId })) + const locations = locationsArr.map((orgLocationId) => ({ orgLocationId })) + + return prisma.orgPhone.upsert({ + where: { id }, + create: { + id, + ...record, + country: connectOneIdRequired(country.id), + phoneType: connectOneId(phoneType), + services: createManyOptional(services), + locations: createManyOptional(locations), + auditLogs, + description: description + ? generateNestedFreeText({ orgId, text: description, type: 'phoneDesc', itemId: id }) + : undefined, + }, + update: { + id, + ...record, + country: connectOneIdRequired(country.id), + phoneType: connectOrDisconnectId(phoneType), + services: diffConnectionsMtoN(services, servicesBefore, 'serviceId'), + locations: diffConnectionsMtoN(locations, locationsBefore, 'orgLocationId'), + description: description + ? { + upsert: { + ...generateNestedFreeText({ + orgId, + text: description, + type: 'phoneDesc', + itemId: id, + }), + update: { tsKey: { update: { text: description } } }, + }, + } + : undefined, + auditLogs, + }, + }) + } + ) + ) + return upserts + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgPhone/mutation.upsertMany.schema.ts b/packages/api/router/orgPhone/mutation.upsertMany.schema.ts new file mode 100644 index 0000000000..78b0ebf25a --- /dev/null +++ b/packages/api/router/orgPhone/mutation.upsertMany.schema.ts @@ -0,0 +1,24 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUpsertManySchema = z.object({ + orgId: prefixedId('organization'), + data: z + .object({ + id: z.string().optional(), + number: z.string(), + ext: z.string().nullish(), + country: z.object({ id: prefixedId('country'), cca2: z.string() }), + phoneType: prefixedId('phoneType').nullish(), + primary: z.boolean(), + published: z.boolean(), + deleted: z.boolean(), + locations: prefixedId('orgLocation').array(), + services: prefixedId('orgService').array(), + description: z.string().optional(), + }) + .array(), +}) + +export type TUpsertManySchema = z.infer diff --git a/packages/api/router/orgPhone/query.forContactInfo.handler.ts b/packages/api/router/orgPhone/query.forContactInfo.handler.ts new file mode 100644 index 0000000000..e980ecdb20 --- /dev/null +++ b/packages/api/router/orgPhone/query.forContactInfo.handler.ts @@ -0,0 +1,55 @@ +import { isIdFor, type Prisma, prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForContactInfoSchema } from './query.forContactInfo.schema' + +export const forContactInfo = async ({ input }: TRPCHandlerParams) => { + try { + const whereId = (): Prisma.OrgPhoneWhereInput => { + switch (true) { + case isIdFor('organization', input.parentId): { + return { organization: { organization: { id: input.parentId, ...globalWhere.isPublic() } } } + } + case isIdFor('orgLocation', input.parentId): { + return { locations: { some: { location: { id: input.parentId, ...globalWhere.isPublic() } } } } + } + case isIdFor('orgService', input.parentId): { + return { services: { some: { service: { id: input.parentId, ...globalWhere.isPublic() } } } } + } + default: { + return {} + } + } + } + + const result = await prisma.orgPhone.findMany({ + where: { + ...globalWhere.isPublic(), + ...whereId(), + ...(input.locationOnly !== undefined ? { locationOnly: input.locationOnly } : {}), + }, + select: { + id: true, + number: true, + ext: true, + country: { select: { cca2: true } }, + primary: true, + description: { select: { tsKey: { select: { text: true, key: true } } } }, + phoneType: { select: { key: { select: { text: true, key: true } } } }, + locationOnly: true, + }, + orderBy: { primary: 'desc' }, + }) + const transformed = result.map(({ description, phoneType, country, ...record }) => ({ + ...record, + country: country?.cca2, + phoneType: phoneType ? { key: phoneType?.key.key, defaultText: phoneType?.key.text } : null, + description: description ? { key: description?.tsKey.key, defaultText: description?.tsKey.text } : null, + })) + return transformed + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgPhone/query.forContactInfo.schema.ts b/packages/api/router/orgPhone/query.forContactInfo.schema.ts new file mode 100644 index 0000000000..78411bcf3c --- /dev/null +++ b/packages/api/router/orgPhone/query.forContactInfo.schema.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForContactInfoSchema = z.object({ + parentId: z.union([prefixedId('organization'), prefixedId('orgLocation'), prefixedId('orgService')]), + locationOnly: z.boolean().optional(), +}) +export type TForContactInfoSchema = z.infer diff --git a/packages/api/router/orgPhone/query.get.handler.ts b/packages/api/router/orgPhone/query.get.handler.ts new file mode 100644 index 0000000000..57b5abd4f9 --- /dev/null +++ b/packages/api/router/orgPhone/query.get.handler.ts @@ -0,0 +1,61 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetSchema } from './query.get.schema' + +export const get = async ({ input }: TRPCHandlerParams) => { + try { + const { id, orgLocationId, organizationId, serviceId } = input + + const result = await prisma.orgPhone.findMany({ + where: { + id, + ...(organizationId ? { organization: { organizationId } } : {}), + ...(orgLocationId ? { locations: { some: { orgLocationId } } } : {}), + ...(serviceId ? { services: { some: { serviceId } } } : {}), + }, + select: { + id: true, + number: true, + ext: true, + deleted: true, + primary: true, + published: true, + country: { select: { cca2: true, id: true } }, + description: { select: { tsKey: { select: { text: true } } } }, + locations: { select: { location: { select: { id: true, name: true } } } }, + organization: { select: { organization: { select: { id: true, name: true, slug: true } } } }, + phoneType: { select: { id: true, type: true, tsKey: true, tsNs: true } }, + services: { + select: { + service: { + select: { + id: true, + serviceName: { select: { tsKey: { select: { text: true } } } }, + }, + }, + }, + }, + }, + orderBy: [{ published: 'desc' }, { deleted: 'desc' }], + }) + + const transformedResult = result.map( + ({ description, locations, organization, phoneType, services, ...record }) => ({ + ...record, + description: description?.tsKey.text, + locations: locations?.map(({ location }) => ({ ...location })), + organization: { ...organization?.organization }, + phoneType, + services: services?.map(({ service }) => ({ + id: service.id, + serviceName: service.serviceName?.tsKey.text, + })), + }) + ) + return transformedResult + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgPhone/query.get.schema.ts b/packages/api/router/orgPhone/query.get.schema.ts new file mode 100644 index 0000000000..05d5327755 --- /dev/null +++ b/packages/api/router/orgPhone/query.get.schema.ts @@ -0,0 +1,31 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetSchema = z.union([ + z.object({ + id: prefixedId('orgPhone'), + organizationId: z.never(), + orgLocationId: z.never(), + serviceId: z.never(), + }), + z.object({ + id: z.never(), + organizationId: prefixedId('organization'), + orgLocationId: z.never(), + serviceId: z.never(), + }), + z.object({ + id: z.never(), + organizationId: z.never(), + orgLocationId: prefixedId('orgLocation'), + serviceId: z.never(), + }), + z.object({ + id: z.never(), + organizationId: z.never(), + orgLocationId: z.never(), + serviceId: prefixedId('orgService'), + }), +]) +export type TGetSchema = z.infer diff --git a/packages/api/router/orgPhone/schemas.ts b/packages/api/router/orgPhone/schemas.ts new file mode 100644 index 0000000000..39890d6f7b --- /dev/null +++ b/packages/api/router/orgPhone/schemas.ts @@ -0,0 +1,7 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.create.schema' +export * from './mutation.update.schema' +export * from './mutation.upsertMany.schema' +export * from './query.forContactInfo.schema' +export * from './query.get.schema' +// codegen:end From cf9d6eebb29fa27c89d244c8f17708ad5fc503a4 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 18:52:31 -0400 Subject: [PATCH 29/63] orgPhoto router caching/lazy load --- packages/api/router/orgPhoto/index.ts | 91 +++++-------------- .../orgPhoto/mutation.create.handler.ts | 22 +++++ .../router/orgPhoto/mutation.create.schema.ts | 26 ++++++ .../orgPhoto/mutation.update.handler.ts | 32 +++++++ .../router/orgPhoto/mutation.update.schema.ts | 22 +++++ .../orgPhoto/query.getByParent.handler.ts | 39 ++++++++ .../orgPhoto/query.getByParent.schema.ts | 6 ++ packages/api/router/orgPhoto/schemas.ts | 5 + 8 files changed, 177 insertions(+), 66 deletions(-) create mode 100644 packages/api/router/orgPhoto/mutation.create.handler.ts create mode 100644 packages/api/router/orgPhoto/mutation.create.schema.ts create mode 100644 packages/api/router/orgPhoto/mutation.update.handler.ts create mode 100644 packages/api/router/orgPhoto/mutation.update.schema.ts create mode 100644 packages/api/router/orgPhoto/query.getByParent.handler.ts create mode 100644 packages/api/router/orgPhoto/query.getByParent.schema.ts create mode 100644 packages/api/router/orgPhoto/schemas.ts diff --git a/packages/api/router/orgPhoto/index.ts b/packages/api/router/orgPhoto/index.ts index 49dba4ee53..823fb22407 100644 --- a/packages/api/router/orgPhoto/index.ts +++ b/packages/api/router/orgPhoto/index.ts @@ -1,76 +1,35 @@ -import { z } from 'zod' - -import { getIdPrefixRegex, isIdFor, type Prisma } from '@weareinreach/db' import { defineRouter, permissionedProcedure, publicProcedure } from '~api/lib/trpc' -import { CreateAuditLog } from '~api/schemas/create/auditLog' -import { CreateOrgPhotoSchema, UpdateOrgPhotoSchema } from '~api/schemas/create/orgPhoto' -import { isPublic } from '~api/schemas/selects/common' + +import * as schema from './schemas' + +const HandlerCache: Partial = {} +type OrgPhotoHandlerCache = { + create: typeof import('./mutation.create.handler').create + update: typeof import('./mutation.update.handler').update + getByParent: typeof import('./query.getByParent.handler').getByParent +} export const orgPhotoRouter = defineRouter({ create: permissionedProcedure('createPhoto') - .input(CreateOrgPhotoSchema) + .input(schema.ZCreateSchema) .mutation(async ({ ctx, input }) => { - const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) - const newRecord = await ctx.prisma.orgPhoto.create({ - data: { - ...input, - auditLogs, - }, - select: { id: true }, - }) - return newRecord + if (!HandlerCache.create) + HandlerCache.create = await import('./mutation.create.handler').then((mod) => mod.create) + if (!HandlerCache.create) throw new Error('Failed to load handler') + return HandlerCache.create({ ctx, input }) }), update: permissionedProcedure('updatePhoto') - .input(UpdateOrgPhotoSchema) + .input(schema.ZUpdateSchema) .mutation(async ({ input, ctx }) => { - const { where, data } = input - const updatedRecord = await ctx.prisma.$transaction(async (tx) => { - const current = await tx.orgPhoto.findUniqueOrThrow({ where }) - const auditLogs = CreateAuditLog({ - actorId: ctx.session.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const updated = await tx.orgPhoto.update({ - where, - data: { - ...data, - auditLogs, - }, - }) - return updated - }) - return updatedRecord - }), - getByParent: publicProcedure - .input(z.string().regex(getIdPrefixRegex('organization', 'orgLocation'))) - .query(async ({ input, ctx }) => { - const whereId = (): Prisma.OrgPhotoWhereInput => { - switch (true) { - case isIdFor('organization', input): { - return { organization: { id: input, ...isPublic } } - } - case isIdFor('orgLocation', input): { - return { orgLocation: { id: input, ...isPublic } } - } - default: { - return {} - } - } - } - const result = await ctx.prisma.orgPhoto.findMany({ - where: { - ...isPublic, - ...whereId(), - }, - select: { - id: true, - src: true, - height: true, - width: true, - }, - }) - return result + if (!HandlerCache.update) + HandlerCache.update = await import('./mutation.update.handler').then((mod) => mod.update) + if (!HandlerCache.update) throw new Error('Failed to load handler') + return HandlerCache.update({ ctx, input }) }), + getByParent: publicProcedure.input(schema.ZGetByParentSchema).query(async ({ input, ctx }) => { + if (!HandlerCache.getByParent) + HandlerCache.getByParent = await import('./query.getByParent.handler').then((mod) => mod.getByParent) + if (!HandlerCache.getByParent) throw new Error('Failed to load handler') + return HandlerCache.getByParent({ ctx, input }) + }), }) diff --git a/packages/api/router/orgPhoto/mutation.create.handler.ts b/packages/api/router/orgPhoto/mutation.create.handler.ts new file mode 100644 index 0000000000..1d355c03ef --- /dev/null +++ b/packages/api/router/orgPhoto/mutation.create.handler.ts @@ -0,0 +1,22 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateSchema } from './mutation.create.schema' + +export const create = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) + const newRecord = await prisma.orgPhoto.create({ + data: { + ...input, + auditLogs, + }, + select: { id: true }, + }) + return newRecord + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgPhoto/mutation.create.schema.ts b/packages/api/router/orgPhoto/mutation.create.schema.ts new file mode 100644 index 0000000000..01ae89af6a --- /dev/null +++ b/packages/api/router/orgPhoto/mutation.create.schema.ts @@ -0,0 +1,26 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZCreateSchema = z + .object({ + src: z.string(), + height: z.number().optional(), + width: z.number().optional(), + published: z.boolean(), + orgId: prefixedId('organization').optional(), + orgLocationId: prefixedId('orgLocation').optional(), + }) + .transform((data) => { + const { src, height, width, published, orgId, orgLocationId } = data + return Prisma.validator()({ + src, + height, + width, + published, + orgId, + orgLocationId, + }) + }) +export type TCreateSchema = z.infer diff --git a/packages/api/router/orgPhoto/mutation.update.handler.ts b/packages/api/router/orgPhoto/mutation.update.handler.ts new file mode 100644 index 0000000000..59e563f314 --- /dev/null +++ b/packages/api/router/orgPhoto/mutation.update.handler.ts @@ -0,0 +1,32 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUpdateSchema } from './mutation.update.schema' + +export const update = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgPhoto.findUniqueOrThrow({ where }) + const auditLogs = CreateAuditLog({ + actorId: ctx.session.user.id, + operation: 'UPDATE', + from: current, + to: data, + }) + const updated = await tx.orgPhoto.update({ + where, + data: { + ...data, + auditLogs, + }, + }) + return updated + }) + return updatedRecord + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgPhoto/mutation.update.schema.ts b/packages/api/router/orgPhoto/mutation.update.schema.ts new file mode 100644 index 0000000000..68a85f5c9c --- /dev/null +++ b/packages/api/router/orgPhoto/mutation.update.schema.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUpdateSchema = z + .object({ + id: prefixedId('orgPhoto'), + data: z + .object({ + src: z.string(), + height: z.number(), + width: z.number(), + published: z.boolean(), + deleted: z.boolean(), + orgId: prefixedId('organization'), + orgLocationId: prefixedId('orgLocation'), + }) + .partial(), + }) + .transform(({ data, id }) => Prisma.validator()({ where: { id }, data })) +export type TUpdateSchema = z.infer diff --git a/packages/api/router/orgPhoto/query.getByParent.handler.ts b/packages/api/router/orgPhoto/query.getByParent.handler.ts new file mode 100644 index 0000000000..f096ca1741 --- /dev/null +++ b/packages/api/router/orgPhoto/query.getByParent.handler.ts @@ -0,0 +1,39 @@ +import { isIdFor, type Prisma, prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetByParentSchema } from './query.getByParent.schema' + +export const getByParent = async ({ input }: TRPCHandlerParams) => { + try { + const whereId = (): Prisma.OrgPhotoWhereInput => { + switch (true) { + case isIdFor('organization', input): { + return { organization: { id: input, ...globalWhere.isPublic() } } + } + case isIdFor('orgLocation', input): { + return { orgLocation: { id: input, ...globalWhere.isPublic() } } + } + default: { + return {} + } + } + } + const result = await prisma.orgPhoto.findMany({ + where: { + ...globalWhere.isPublic(), + ...whereId(), + }, + select: { + id: true, + src: true, + height: true, + width: true, + }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgPhoto/query.getByParent.schema.ts b/packages/api/router/orgPhoto/query.getByParent.schema.ts new file mode 100644 index 0000000000..4e10d629f6 --- /dev/null +++ b/packages/api/router/orgPhoto/query.getByParent.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetByParentSchema = z.union([prefixedId('organization'), prefixedId('orgLocation')]) +export type TGetByParentSchema = z.infer diff --git a/packages/api/router/orgPhoto/schemas.ts b/packages/api/router/orgPhoto/schemas.ts new file mode 100644 index 0000000000..46d61b74a2 --- /dev/null +++ b/packages/api/router/orgPhoto/schemas.ts @@ -0,0 +1,5 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.create.schema' +export * from './mutation.update.schema' +export * from './query.getByParent.schema' +// codegen:end From d5d8fbbda8f4e4d6de855a4027f1d7e84700cdf7 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 19:00:51 -0400 Subject: [PATCH 30/63] orgSocialMedia router caching/lazy load --- packages/api/router/orgSocialMedia/index.ts | 104 +++++------------- .../orgSocialMedia/mutation.create.handler.ts | 22 ++++ .../orgSocialMedia/mutation.create.schema.ts | 17 +++ .../orgSocialMedia/mutation.update.handler.ts | 32 ++++++ .../orgSocialMedia/mutation.update.schema.ts | 23 ++++ .../query.forContactInfo.handler.ts | 46 ++++++++ .../query.forContactInfo.schema.ts | 9 ++ packages/api/router/orgSocialMedia/schemas.ts | 5 + 8 files changed, 180 insertions(+), 78 deletions(-) create mode 100644 packages/api/router/orgSocialMedia/mutation.create.handler.ts create mode 100644 packages/api/router/orgSocialMedia/mutation.create.schema.ts create mode 100644 packages/api/router/orgSocialMedia/mutation.update.handler.ts create mode 100644 packages/api/router/orgSocialMedia/mutation.update.schema.ts create mode 100644 packages/api/router/orgSocialMedia/query.forContactInfo.handler.ts create mode 100644 packages/api/router/orgSocialMedia/query.forContactInfo.schema.ts create mode 100644 packages/api/router/orgSocialMedia/schemas.ts diff --git a/packages/api/router/orgSocialMedia/index.ts b/packages/api/router/orgSocialMedia/index.ts index d83d6b2882..687dfb3cdf 100644 --- a/packages/api/router/orgSocialMedia/index.ts +++ b/packages/api/router/orgSocialMedia/index.ts @@ -1,88 +1,36 @@ -import { z } from 'zod' - -import { getIdPrefixRegex, isIdFor, type Prisma } from '@weareinreach/db' import { defineRouter, permissionedProcedure, publicProcedure } from '~api/lib/trpc' -import { CreateAuditLog } from '~api/schemas/create/auditLog' -import { CreateOrgSocialSchema, UpdateOrgSocialSchema } from '~api/schemas/create/orgSocialMedia' -import { isPublic } from '~api/schemas/selects/common' +import * as schema from './schemas' + +const HandlerCache: Partial = {} +type OrgSocialMediaHandlerCache = { + create: typeof import('./mutation.create.handler').create + update: typeof import('./mutation.update.handler').update + forContactInfo: typeof import('./query.forContactInfo.handler').forContactInfo +} export const orgSocialMediaRouter = defineRouter({ create: permissionedProcedure('createNewSocial') - .input(CreateOrgSocialSchema) + .input(schema.ZCreateSchema) .mutation(async ({ ctx, input }) => { - const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) - const newSocial = await ctx.prisma.orgSocialMedia.create({ - data: { - ...input, - auditLogs, - }, - select: { id: true }, - }) - return newSocial + if (!HandlerCache.create) + HandlerCache.create = await import('./mutation.create.handler').then((mod) => mod.create) + if (!HandlerCache.create) throw new Error('Failed to load handler') + return HandlerCache.create({ ctx, input }) }), update: permissionedProcedure('updateSocialMedia') - .input(UpdateOrgSocialSchema) + .input(schema.ZUpdateSchema) .mutation(async ({ input, ctx }) => { - const { where, data } = input - const updatedRecord = await ctx.prisma.$transaction(async (tx) => { - const current = await tx.orgSocialMedia.findUniqueOrThrow({ where }) - const auditLogs = CreateAuditLog({ - actorId: ctx.session.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const updated = await tx.orgSocialMedia.update({ - where, - data: { - ...data, - auditLogs, - }, - }) - return updated - }) - return updatedRecord - }), - forContactInfo: publicProcedure - .input( - z.object({ - parentId: z.string().regex(getIdPrefixRegex('organization', 'orgLocation')), - locationOnly: z.boolean().optional(), - }) - ) - .query(async ({ ctx, input }) => { - const whereId = (): Prisma.OrgSocialMediaWhereInput => { - switch (true) { - case isIdFor('organization', input.parentId): { - return { organization: { id: input.parentId, ...isPublic } } - } - case isIdFor('orgLocation', input.parentId): { - return { orgLocation: { id: input.parentId, ...isPublic } } - } - default: { - return {} - } - } - } - - const result = await ctx.prisma.orgSocialMedia.findMany({ - where: { - ...isPublic, - ...whereId(), - ...(input.locationOnly !== undefined ? { locationOnly: input.locationOnly } : {}), - }, - select: { - id: true, - url: true, - username: true, - service: { select: { name: true } }, - orgLocationOnly: true, - }, - }) - const transformed = result.map(({ service, ...record }) => ({ - ...record, - service: service?.name, - })) - return transformed + if (!HandlerCache.update) + HandlerCache.update = await import('./mutation.update.handler').then((mod) => mod.update) + if (!HandlerCache.update) throw new Error('Failed to load handler') + return HandlerCache.update({ ctx, input }) }), + forContactInfo: publicProcedure.input(schema.ZForContactInfoSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forContactInfo) + HandlerCache.forContactInfo = await import('./query.forContactInfo.handler').then( + (mod) => mod.forContactInfo + ) + if (!HandlerCache.forContactInfo) throw new Error('Failed to load handler') + return HandlerCache.forContactInfo({ ctx, input }) + }), }) diff --git a/packages/api/router/orgSocialMedia/mutation.create.handler.ts b/packages/api/router/orgSocialMedia/mutation.create.handler.ts new file mode 100644 index 0000000000..0fc60a3ca4 --- /dev/null +++ b/packages/api/router/orgSocialMedia/mutation.create.handler.ts @@ -0,0 +1,22 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateSchema } from './mutation.create.schema' + +export const create = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) + const newSocial = await prisma.orgSocialMedia.create({ + data: { + ...input, + auditLogs, + }, + select: { id: true }, + }) + return newSocial + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgSocialMedia/mutation.create.schema.ts b/packages/api/router/orgSocialMedia/mutation.create.schema.ts new file mode 100644 index 0000000000..55a5dfeea7 --- /dev/null +++ b/packages/api/router/orgSocialMedia/mutation.create.schema.ts @@ -0,0 +1,17 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZCreateSchema = z + .object({ + username: z.string(), + url: z.string(), + published: z.boolean(), + serviceId: z.string(), + organizationId: prefixedId('organization').optional(), + orgLocationId: prefixedId('orgLocation').optional(), + orgLocationOnly: z.boolean(), + }) + .transform((data) => Prisma.validator()(data)) +export type TCreateSchema = z.infer diff --git a/packages/api/router/orgSocialMedia/mutation.update.handler.ts b/packages/api/router/orgSocialMedia/mutation.update.handler.ts new file mode 100644 index 0000000000..bece7aaa86 --- /dev/null +++ b/packages/api/router/orgSocialMedia/mutation.update.handler.ts @@ -0,0 +1,32 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUpdateSchema } from './mutation.update.schema' + +export const update = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgSocialMedia.findUniqueOrThrow({ where }) + const auditLogs = CreateAuditLog({ + actorId: ctx.session.user.id, + operation: 'UPDATE', + from: current, + to: data, + }) + const updated = await tx.orgSocialMedia.update({ + where, + data: { + ...data, + auditLogs, + }, + }) + return updated + }) + return updatedRecord + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgSocialMedia/mutation.update.schema.ts b/packages/api/router/orgSocialMedia/mutation.update.schema.ts new file mode 100644 index 0000000000..c87f08e25f --- /dev/null +++ b/packages/api/router/orgSocialMedia/mutation.update.schema.ts @@ -0,0 +1,23 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUpdateSchema = z + .object({ + id: prefixedId('orgSocialMedia'), + data: z + .object({ + username: z.string(), + url: z.string(), + published: z.boolean(), + deleted: z.boolean(), + serviceId: z.string(), + organizationId: prefixedId('organization'), + orgLocationId: prefixedId('orgLocation'), + orgLocationOnly: z.boolean(), + }) + .partial(), + }) + .transform(({ data, id }) => Prisma.validator()({ where: { id }, data })) +export type TUpdateSchema = z.infer diff --git a/packages/api/router/orgSocialMedia/query.forContactInfo.handler.ts b/packages/api/router/orgSocialMedia/query.forContactInfo.handler.ts new file mode 100644 index 0000000000..1f36dd941d --- /dev/null +++ b/packages/api/router/orgSocialMedia/query.forContactInfo.handler.ts @@ -0,0 +1,46 @@ +import { isIdFor, type Prisma, prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForContactInfoSchema } from './query.forContactInfo.schema' + +export const forContactInfo = async ({ input }: TRPCHandlerParams) => { + try { + const whereId = (): Prisma.OrgSocialMediaWhereInput => { + switch (true) { + case isIdFor('organization', input.parentId): { + return { organization: { id: input.parentId, ...globalWhere.isPublic() } } + } + case isIdFor('orgLocation', input.parentId): { + return { orgLocation: { id: input.parentId, ...globalWhere.isPublic() } } + } + default: { + return {} + } + } + } + + const result = await prisma.orgSocialMedia.findMany({ + where: { + ...globalWhere.isPublic(), + ...whereId(), + ...(input.locationOnly !== undefined ? { locationOnly: input.locationOnly } : {}), + }, + select: { + id: true, + url: true, + username: true, + service: { select: { name: true } }, + orgLocationOnly: true, + }, + }) + const transformed = result.map(({ service, ...record }) => ({ + ...record, + service: service?.name, + })) + return transformed + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgSocialMedia/query.forContactInfo.schema.ts b/packages/api/router/orgSocialMedia/query.forContactInfo.schema.ts new file mode 100644 index 0000000000..36af3dfd1d --- /dev/null +++ b/packages/api/router/orgSocialMedia/query.forContactInfo.schema.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForContactInfoSchema = z.object({ + parentId: z.union([prefixedId('organization'), prefixedId('orgLocation')]), + locationOnly: z.boolean().optional(), +}) +export type TForContactInfoSchema = z.infer diff --git a/packages/api/router/orgSocialMedia/schemas.ts b/packages/api/router/orgSocialMedia/schemas.ts new file mode 100644 index 0000000000..57e4fa8f25 --- /dev/null +++ b/packages/api/router/orgSocialMedia/schemas.ts @@ -0,0 +1,5 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.create.schema' +export * from './mutation.update.schema' +export * from './query.forContactInfo.schema' +// codegen:end From 9474e3b2fe2dd4ab350eb607fe39bc8f793cf783 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 19:09:39 -0400 Subject: [PATCH 31/63] orgWebsite router caching/lazy load --- packages/api/router/orgWebsite/index.ts | 110 +++++------------- .../orgWebsite/mutation.create.handler.ts | 22 ++++ .../orgWebsite/mutation.create.schema.ts | 38 ++++++ .../orgWebsite/mutation.update.handler.ts | 32 +++++ .../orgWebsite/mutation.update.schema.ts | 22 ++++ .../query.forContactInfo.handler.ts | 50 ++++++++ .../orgWebsite/query.forContactInfo.schema.ts | 9 ++ packages/api/router/orgWebsite/schemas.ts | 5 + 8 files changed, 204 insertions(+), 84 deletions(-) create mode 100644 packages/api/router/orgWebsite/mutation.create.handler.ts create mode 100644 packages/api/router/orgWebsite/mutation.create.schema.ts create mode 100644 packages/api/router/orgWebsite/mutation.update.handler.ts create mode 100644 packages/api/router/orgWebsite/mutation.update.schema.ts create mode 100644 packages/api/router/orgWebsite/query.forContactInfo.handler.ts create mode 100644 packages/api/router/orgWebsite/query.forContactInfo.schema.ts create mode 100644 packages/api/router/orgWebsite/schemas.ts diff --git a/packages/api/router/orgWebsite/index.ts b/packages/api/router/orgWebsite/index.ts index 4347d3139a..c180c06f1a 100644 --- a/packages/api/router/orgWebsite/index.ts +++ b/packages/api/router/orgWebsite/index.ts @@ -1,94 +1,36 @@ -import { z } from 'zod' - -import { getIdPrefixRegex, isIdFor, type Prisma } from '@weareinreach/db' import { defineRouter, permissionedProcedure, publicProcedure } from '~api/lib/trpc' -import { CreateAuditLog } from '~api/schemas/create/auditLog' -import { CreateOrgWebsiteSchema, UpdateOrgWebsiteSchema } from '~api/schemas/create/orgWebsite' -import { isPublic } from '~api/schemas/selects/common' +import * as schema from './schemas' + +const HandlerCache: Partial = {} +type OrgWebsiteHandlerCache = { + create: typeof import('./mutation.create.handler').create + update: typeof import('./mutation.update.handler').update + forContactInfo: typeof import('./query.forContactInfo.handler').forContactInfo +} export const orgWebsiteRouter = defineRouter({ create: permissionedProcedure('createOrgWebsite') - .input(CreateOrgWebsiteSchema) + .input(schema.ZCreateSchema) .mutation(async ({ ctx, input }) => { - const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) - const newRecord = await ctx.prisma.orgWebsite.create({ - data: { - ...input, - auditLogs, - }, - select: { id: true }, - }) - return newRecord + if (!HandlerCache.create) + HandlerCache.create = await import('./mutation.create.handler').then((mod) => mod.create) + if (!HandlerCache.create) throw new Error('Failed to load handler') + return HandlerCache.create({ ctx, input }) }), update: permissionedProcedure('updateOrgWebsite') - .input(UpdateOrgWebsiteSchema) + .input(schema.ZUpdateSchema) .mutation(async ({ input, ctx }) => { - const { where, data } = input - const updatedRecord = await ctx.prisma.$transaction(async (tx) => { - const current = await tx.orgWebsite.findUniqueOrThrow({ where }) - const auditLogs = CreateAuditLog({ - actorId: ctx.session.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const updated = await tx.orgWebsite.update({ - where, - data: { - ...data, - auditLogs, - }, - }) - return updated - }) - return updatedRecord - }), - forContactInfo: publicProcedure - .input( - z.object({ - parentId: z.string().regex(getIdPrefixRegex('organization', 'orgLocation' /*, 'orgService'*/)), - locationOnly: z.boolean().optional(), - }) - ) - .query(async ({ ctx, input }) => { - const whereId = (): Prisma.OrgWebsiteWhereInput => { - switch (true) { - case isIdFor('organization', input.parentId): { - return { organization: { id: input.parentId, ...isPublic } } - } - case isIdFor('orgLocation', input.parentId): { - return { orgLocation: { id: input.parentId, ...isPublic } } - } - // case isIdFor('orgService', input.parentId): { - // return { services: { some: { service: { id: input.parentId, ...isPublic } } } } - // } - default: { - return {} - } - } - } - - const result = await ctx.prisma.orgWebsite.findMany({ - where: { - ...isPublic, - ...whereId(), - ...(input.locationOnly !== undefined ? { orgLocationOnly: input.locationOnly } : {}), - }, - select: { - id: true, - url: true, - isPrimary: true, - description: { select: { tsKey: { select: { text: true, key: true } } } }, - orgLocationOnly: true, - }, - orderBy: { isPrimary: 'desc' }, - }) - const transformed = result.map(({ description, ...record }) => ({ - ...record, - description: description - ? { key: description?.tsKey.key, defaultText: description?.tsKey.text } - : null, - })) - return transformed + if (!HandlerCache.update) + HandlerCache.update = await import('./mutation.update.handler').then((mod) => mod.update) + if (!HandlerCache.update) throw new Error('Failed to load handler') + return HandlerCache.update({ ctx, input }) }), + forContactInfo: publicProcedure.input(schema.ZForContactInfoSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forContactInfo) + HandlerCache.forContactInfo = await import('./query.forContactInfo.handler').then( + (mod) => mod.forContactInfo + ) + if (!HandlerCache.forContactInfo) throw new Error('Failed to load handler') + return HandlerCache.forContactInfo({ ctx, input }) + }), }) diff --git a/packages/api/router/orgWebsite/mutation.create.handler.ts b/packages/api/router/orgWebsite/mutation.create.handler.ts new file mode 100644 index 0000000000..2f562cd5e2 --- /dev/null +++ b/packages/api/router/orgWebsite/mutation.create.handler.ts @@ -0,0 +1,22 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateSchema } from './mutation.create.schema' + +export const create = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) + const newRecord = await prisma.orgWebsite.create({ + data: { + ...input, + auditLogs, + }, + select: { id: true }, + }) + return newRecord + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgWebsite/mutation.create.schema.ts b/packages/api/router/orgWebsite/mutation.create.schema.ts new file mode 100644 index 0000000000..f1067c5f4f --- /dev/null +++ b/packages/api/router/orgWebsite/mutation.create.schema.ts @@ -0,0 +1,38 @@ +import { z } from 'zod' + +import { generateId, generateNestedFreeText, Prisma } from '@weareinreach/db' +import { prefixedId } from '~api/schemas/idPrefix' +import { connectOneId } from '~api/schemas/nestedOps' + +export const ZCreateSchema = z + .object({ + orgId: prefixedId('organization'), + data: z.object({ + url: z.string(), + isPrimary: z.boolean().optional(), + published: z.boolean().optional(), + organizationId: prefixedId('organization').optional(), + orgLocationId: prefixedId('orgLocation').optional(), + orgLocationOnly: z.boolean(), + description: z.string().optional(), + }), + }) + .transform(({ data, orgId }) => { + const id = generateId('orgWebsite') + const description = data.description + ? generateNestedFreeText({ orgId, itemId: id, text: data.description, type: 'websiteDesc' }) + : undefined + + const { url, isPrimary, published, organizationId, orgLocationId, orgLocationOnly } = data + return Prisma.validator()({ + id, + url, + isPrimary, + published, + orgLocationOnly, + description, + organization: connectOneId(organizationId), + orgLocation: connectOneId(orgLocationId), + }) + }) +export type TCreateSchema = z.infer diff --git a/packages/api/router/orgWebsite/mutation.update.handler.ts b/packages/api/router/orgWebsite/mutation.update.handler.ts new file mode 100644 index 0000000000..d9de09a561 --- /dev/null +++ b/packages/api/router/orgWebsite/mutation.update.handler.ts @@ -0,0 +1,32 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUpdateSchema } from './mutation.update.schema' + +export const update = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgWebsite.findUniqueOrThrow({ where }) + const auditLogs = CreateAuditLog({ + actorId: ctx.session.user.id, + operation: 'UPDATE', + from: current, + to: data, + }) + const updated = await tx.orgWebsite.update({ + where, + data: { + ...data, + auditLogs, + }, + }) + return updated + }) + return updatedRecord + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgWebsite/mutation.update.schema.ts b/packages/api/router/orgWebsite/mutation.update.schema.ts new file mode 100644 index 0000000000..fd8dbee204 --- /dev/null +++ b/packages/api/router/orgWebsite/mutation.update.schema.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUpdateSchema = z + .object({ + id: prefixedId('orgWebsite'), + data: z + .object({ + url: z.string(), + isPrimary: z.boolean(), + published: z.boolean(), + deleted: z.boolean(), + organizationId: prefixedId('organization'), + orgLocationId: prefixedId('orgLocation'), + orgLocationOnly: z.boolean(), + }) + .partial(), + }) + .transform(({ data, id }) => Prisma.validator()({ where: { id }, data })) +export type TUpdateSchema = z.infer diff --git a/packages/api/router/orgWebsite/query.forContactInfo.handler.ts b/packages/api/router/orgWebsite/query.forContactInfo.handler.ts new file mode 100644 index 0000000000..2b7c47ca78 --- /dev/null +++ b/packages/api/router/orgWebsite/query.forContactInfo.handler.ts @@ -0,0 +1,50 @@ +import { isIdFor, prisma, type Prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { globalWhere } from '~api/selects/global' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForContactInfoSchema } from './query.forContactInfo.schema' + +export const forContactInfo = async ({ input }: TRPCHandlerParams) => { + try { + const whereId = (): Prisma.OrgWebsiteWhereInput => { + switch (true) { + case isIdFor('organization', input.parentId): { + return { organization: { id: input.parentId, ...globalWhere.isPublic() } } + } + case isIdFor('orgLocation', input.parentId): { + return { orgLocation: { id: input.parentId, ...globalWhere.isPublic() } } + } + // case isIdFor('orgService', input.parentId): { + // return { services: { some: { service: { id: input.parentId, ...globalWhere.isPublic() } } } } + // } + default: { + return {} + } + } + } + + const result = await prisma.orgWebsite.findMany({ + where: { + ...globalWhere.isPublic(), + ...whereId(), + ...(input.locationOnly !== undefined ? { orgLocationOnly: input.locationOnly } : {}), + }, + select: { + id: true, + url: true, + isPrimary: true, + description: { select: { tsKey: { select: { text: true, key: true } } } }, + orgLocationOnly: true, + }, + orderBy: { isPrimary: 'desc' }, + }) + const transformed = result.map(({ description, ...record }) => ({ + ...record, + description: description ? { key: description?.tsKey.key, defaultText: description?.tsKey.text } : null, + })) + return transformed + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/orgWebsite/query.forContactInfo.schema.ts b/packages/api/router/orgWebsite/query.forContactInfo.schema.ts new file mode 100644 index 0000000000..36af3dfd1d --- /dev/null +++ b/packages/api/router/orgWebsite/query.forContactInfo.schema.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZForContactInfoSchema = z.object({ + parentId: z.union([prefixedId('organization'), prefixedId('orgLocation')]), + locationOnly: z.boolean().optional(), +}) +export type TForContactInfoSchema = z.infer diff --git a/packages/api/router/orgWebsite/schemas.ts b/packages/api/router/orgWebsite/schemas.ts new file mode 100644 index 0000000000..57e4fa8f25 --- /dev/null +++ b/packages/api/router/orgWebsite/schemas.ts @@ -0,0 +1,5 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.create.schema' +export * from './mutation.update.schema' +export * from './query.forContactInfo.schema' +// codegen:end From 72ca510549205de114f71654bd0798c9cc9c63eb Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 19:26:09 -0400 Subject: [PATCH 32/63] quicklink router caching/lazy load --- packages/api/router/quicklink/index.ts | 420 +++--------------- .../mutation.updateEmailData.handler.ts | 45 ++ .../mutation.updateEmailData.schema.ts | 30 ++ .../mutation.updatePhoneData.handler.ts | 45 ++ .../mutation.updatePhoneData.schema.ts | 30 ++ ...ation.updateServiceLocationData.handler.ts | 37 ++ ...tation.updateServiceLocationData.schema.ts | 22 + .../quicklink/query.getEmailData.handler.ts | 89 ++++ .../quicklink/query.getEmailData.schema.ts | 4 + .../quicklink/query.getPhoneData.handler.ts | 95 ++++ .../quicklink/query.getPhoneData.schema.ts | 4 + .../query.getServiceLocationData.handler.ts | 73 +++ .../query.getServiceLocationData.schema.ts | 4 + packages/api/router/quicklink/schemas.ts | 8 + 14 files changed, 540 insertions(+), 366 deletions(-) create mode 100644 packages/api/router/quicklink/mutation.updateEmailData.handler.ts create mode 100644 packages/api/router/quicklink/mutation.updateEmailData.schema.ts create mode 100644 packages/api/router/quicklink/mutation.updatePhoneData.handler.ts create mode 100644 packages/api/router/quicklink/mutation.updatePhoneData.schema.ts create mode 100644 packages/api/router/quicklink/mutation.updateServiceLocationData.handler.ts create mode 100644 packages/api/router/quicklink/mutation.updateServiceLocationData.schema.ts create mode 100644 packages/api/router/quicklink/query.getEmailData.handler.ts create mode 100644 packages/api/router/quicklink/query.getEmailData.schema.ts create mode 100644 packages/api/router/quicklink/query.getPhoneData.handler.ts create mode 100644 packages/api/router/quicklink/query.getPhoneData.schema.ts create mode 100644 packages/api/router/quicklink/query.getServiceLocationData.handler.ts create mode 100644 packages/api/router/quicklink/query.getServiceLocationData.schema.ts create mode 100644 packages/api/router/quicklink/schemas.ts diff --git a/packages/api/router/quicklink/index.ts b/packages/api/router/quicklink/index.ts index 400ac29988..3b5ea5ed35 100644 --- a/packages/api/router/quicklink/index.ts +++ b/packages/api/router/quicklink/index.ts @@ -1,389 +1,77 @@ -import flush from 'just-flush' -import { z } from 'zod' - -import { type Prisma } from '@weareinreach/db/client' import { defineRouter, permissionedProcedure } from '~api/lib/trpc' -import { CreateAuditLog } from '~api/schemas/create/auditLog' -const phoneSelect = { - select: { - phone: { - select: { - id: true, - number: true, - description: { select: { tsKey: { select: { text: true } } } }, - country: { select: { id: true, cca2: true } }, - locationOnly: true, - serviceOnly: true, - locations: { select: { location: { select: { id: true } } } }, - services: { select: { service: { select: { id: true } } } }, - published: true, - }, - }, - }, -} satisfies Prisma.Organization$phonesArgs +import * as schema from './schemas' + +const HandlerCache: Partial = {} + +type QuicklinkHandlerCache = { + getEmailData: typeof import('./query.getEmailData.handler').getEmailData + updateEmailData: typeof import('./mutation.updateEmailData.handler').updateEmailData + getPhoneData: typeof import('./query.getPhoneData.handler').getPhoneData + updatePhoneData: typeof import('./mutation.updatePhoneData.handler').updatePhoneData + getServiceLocationData: typeof import('./query.getServiceLocationData.handler').getServiceLocationData + updateServiceLocationData: typeof import('./mutation.updateServiceLocationData.handler').updateServiceLocationData +} export const quickLinkRouter = defineRouter({ getPhoneData: permissionedProcedure('updateLocation') - .input(z.object({ limit: z.number(), skip: z.number().optional() })) + .input(schema.ZGetPhoneDataSchema) .query(async ({ ctx, input }) => { - const limit = input.limit ?? 20 - const { skip } = input - - const where = { - published: true, - deleted: false, - phones: { - some: { - phone: { AND: [{ locations: { none: {} } }, { services: { none: {} } }, { published: true }] }, - }, - }, - } satisfies Prisma.OrganizationWhereInput - - const data = await ctx.prisma.organization.findMany({ - where, - select: { - id: true, - name: true, - slug: true, - locations: { - select: { - id: true, - name: true, - // phones: { select: { phone: { select: { id: true } } } }, - }, - }, - phones: phoneSelect, - services: { - select: { - id: true, - serviceName: { select: { tsKey: { select: { text: true } } } }, - // phones: { select: { phone: { select: { id: true } } } }, - }, - }, - }, - orderBy: { id: 'asc' }, - take: limit, // + 1, - skip, - }) - const totalResults = await ctx.prisma.organization.count({ where }) - const transformedData = data.flatMap(({ locations, phones, services, id, name, slug }) => - phones.map(({ phone }) => { - const { - description, - id: phoneId, - locations: attachedLocations, - services: attachedServices, - ...rest - } = phone - return { - orgId: id, - name, - slug, - phoneId, - ...rest, - description: description?.tsKey.text, - attachedLocations: attachedLocations.map(({ location }) => location.id), - attachedServices: attachedServices.map(({ service }) => service.id), - locations, - services, - } - }) - ) - return { - results: transformedData, - totalResults, - } + if (!HandlerCache.getPhoneData) + HandlerCache.getPhoneData = await import('./query.getPhoneData.handler').then( + (mod) => mod.getPhoneData + ) + if (!HandlerCache.getPhoneData) throw new Error('Failed to load handler') + return HandlerCache.getPhoneData({ ctx, input }) }), updatePhoneData: permissionedProcedure('updateLocation') - .input( - z - .object({ - id: z.string(), - from: z - .object({ - serviceOnly: z.boolean().optional(), - locationOnly: z.boolean().optional(), - locations: z.string().array(), - services: z.string().array(), - published: z.boolean().optional(), - }) - .partial(), - to: z.object({ - serviceOnly: z.boolean().optional(), - locationOnly: z.boolean().optional(), - locations: z.object({ add: z.string().array(), del: z.string().array() }).partial(), - services: z.object({ add: z.string().array(), del: z.string().array() }).partial(), - published: z.boolean().optional(), - }), - }) - .array() - ) + .input(schema.ZUpdatePhoneDataSchema) .mutation(async ({ ctx, input }) => { - const updates = input.map(({ id, from, to }) => { - const { serviceOnly, locationOnly, locations, services, published } = to - const auditLogs = CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from, to: flush(to) }) - return ctx.prisma.orgPhone.update({ - where: { id }, - data: { - serviceOnly, - locationOnly, - published, - locations: { - createMany: locations.add?.length - ? { data: locations.add.map((orgLocationId) => ({ orgLocationId })), skipDuplicates: true } - : undefined, - deleteMany: locations.del?.length ? { orgLocationId: { in: locations.del } } : undefined, - }, - services: { - createMany: services.add?.length - ? { data: services.add.map((serviceId) => ({ serviceId })), skipDuplicates: true } - : undefined, - deleteMany: services.del?.length ? { serviceId: { in: services.del } } : undefined, - }, - auditLogs, - }, - }) - }) - const results = await ctx.prisma.$transaction(updates) - return results + if (!HandlerCache.updatePhoneData) + HandlerCache.updatePhoneData = await import('./mutation.updatePhoneData.handler').then( + (mod) => mod.updatePhoneData + ) + if (!HandlerCache.updatePhoneData) throw new Error('Failed to load handler') + return HandlerCache.updatePhoneData({ ctx, input }) }), getEmailData: permissionedProcedure('updateLocation') - .input(z.object({ limit: z.number(), skip: z.number().optional() })) + .input(schema.ZGetEmailDataSchema) .query(async ({ ctx, input }) => { - const limit = input.limit ?? 20 - const { skip } = input - - const where = { - published: true, - deleted: false, - emails: { - some: { - email: { AND: [{ locations: { none: {} } }, { services: { none: {} } }, { published: true }] }, - }, - }, - } satisfies Prisma.OrganizationWhereInput - - const data = await ctx.prisma.organization.findMany({ - where, - select: { - id: true, - name: true, - slug: true, - locations: { select: { id: true, name: true } }, - services: { - select: { - id: true, - serviceName: { select: { tsKey: { select: { text: true } } } }, - }, - }, - emails: { - select: { - email: { - select: { - id: true, - email: true, - firstName: true, - lastName: true, - locationOnly: true, - serviceOnly: true, - published: true, - description: { select: { tsKey: { select: { text: true } } } }, - locations: { select: { location: { select: { id: true } } } }, - services: { select: { service: { select: { id: true } } } }, - }, - }, - }, - }, - }, - orderBy: { id: 'asc' }, - take: limit, // + 1, - skip, - }) - const totalResults = await ctx.prisma.organization.count({ where }) - const transformedData = data.flatMap(({ locations, emails, services, id, name, slug }) => - emails.map(({ email }) => { - const { - description, - id: emailId, - locations: attachedLocations, - services: attachedServices, - ...rest - } = email - return { - orgId: id, - name, - slug, - emailId, - ...rest, - description: description?.tsKey.text, - attachedLocations: attachedLocations.map(({ location }) => location.id), - attachedServices: attachedServices.map(({ service }) => service.id), - locations, - services, - } - }) - ) - return { - results: transformedData, - totalResults, - } + if (!HandlerCache.getEmailData) + HandlerCache.getEmailData = await import('./query.getEmailData.handler').then( + (mod) => mod.getEmailData + ) + if (!HandlerCache.getEmailData) throw new Error('Failed to load handler') + return HandlerCache.getEmailData({ ctx, input }) }), updateEmailData: permissionedProcedure('updateLocation') - .input( - z - .object({ - id: z.string(), - from: z - .object({ - serviceOnly: z.boolean().optional(), - locationOnly: z.boolean().optional(), - published: z.boolean().optional(), - locations: z.string().array(), - services: z.string().array(), - }) - .partial(), - to: z.object({ - serviceOnly: z.boolean().optional(), - locationOnly: z.boolean().optional(), - published: z.boolean().optional(), - locations: z.object({ add: z.string().array(), del: z.string().array() }).partial(), - services: z.object({ add: z.string().array(), del: z.string().array() }).partial(), - }), - }) - .array() - ) + .input(schema.ZUpdateEmailDataSchema) .mutation(async ({ ctx, input }) => { - const updates = input.map(({ id, from, to }) => { - const { serviceOnly, locationOnly, locations, services, published } = to - const auditLogs = CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from, to: flush(to) }) - return ctx.prisma.orgEmail.update({ - where: { id }, - data: { - serviceOnly, - locationOnly, - published, - locations: { - createMany: locations.add?.length - ? { data: locations.add.map((orgLocationId) => ({ orgLocationId })), skipDuplicates: true } - : undefined, - deleteMany: locations.del?.length ? { orgLocationId: { in: locations.del } } : undefined, - }, - services: { - createMany: services.add?.length - ? { data: services.add.map((serviceId) => ({ serviceId })), skipDuplicates: true } - : undefined, - deleteMany: services.del?.length ? { serviceId: { in: services.del } } : undefined, - }, - auditLogs, - }, - }) - }) - const results = await ctx.prisma.$transaction(updates) - return results + if (!HandlerCache.updateEmailData) + HandlerCache.updateEmailData = await import('./mutation.updateEmailData.handler').then( + (mod) => mod.updateEmailData + ) + if (!HandlerCache.updateEmailData) throw new Error('Failed to load handler') + return HandlerCache.updateEmailData({ ctx, input }) }), getServiceLocationData: permissionedProcedure('updateLocation') - .input(z.object({ limit: z.number(), skip: z.number().optional() })) + .input(schema.ZGetServiceLocationDataSchema) .query(async ({ ctx, input }) => { - const limit = input.limit ?? 20 - const { skip } = input - - const where = { - published: true, - deleted: false, - locations: { - some: { services: { none: {} }, published: true }, - }, - } satisfies Prisma.OrganizationWhereInput - const results = await ctx.prisma.organization.findMany({ - where, - select: { - id: true, - name: true, - slug: true, - locations: { - select: { - id: true, - name: true, - published: true, - services: { - select: { - service: { - select: { id: true, serviceName: { select: { tsKey: { select: { text: true } } } } }, - }, - }, - }, - }, - where: { services: { none: {} }, published: true, deleted: false }, - }, - services: { - select: { - id: true, - serviceName: { select: { tsKey: { select: { text: true } } } }, - }, - }, - }, - orderBy: { id: 'asc' }, - take: limit, // + 1, - skip, - }) - const totalResults = await ctx.prisma.organization.count({ where }) - const transformedData = results.flatMap(({ locations, services, id, name, slug }) => - locations.map(({ id: locationId, name: locationName, services: locationServices, published }) => { - return { - orgId: id, - name, - slug, - locationId, - locationName, - published, - attachedServices: locationServices.map(({ service }) => service.id), - services, - } - }) - ) - return { - results: transformedData, - totalResults, - } + if (!HandlerCache.getServiceLocationData) + HandlerCache.getServiceLocationData = await import('./query.getServiceLocationData.handler').then( + (mod) => mod.getServiceLocationData + ) + if (!HandlerCache.getServiceLocationData) throw new Error('Failed to load handler') + return HandlerCache.getServiceLocationData({ ctx, input }) }), updateServiceLocationData: permissionedProcedure('updateLocation') - .input( - z - .object({ - id: z.string(), - from: z - .object({ - services: z.string().array(), - published: z.boolean().optional(), - }) - .partial(), - to: z.object({ - services: z.object({ add: z.string().array(), del: z.string().array() }).partial(), - published: z.boolean().optional(), - }), - }) - .array() - ) + .input(schema.ZUpdateServiceLocationDataSchema) .mutation(async ({ ctx, input }) => { - const updates = input.map(({ id, from, to }) => { - const { services, published } = to - const auditLogs = CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from, to: flush(to) }) - return ctx.prisma.orgLocation.update({ - where: { id }, - data: { - published, - services: { - createMany: services.add?.length - ? { data: services.add.map((serviceId) => ({ serviceId })), skipDuplicates: true } - : undefined, - deleteMany: services.del?.length ? { serviceId: { in: services.del } } : undefined, - }, - auditLogs, - }, - }) - }) - const results = await ctx.prisma.$transaction(updates) - return results + if (!HandlerCache.updateServiceLocationData) + HandlerCache.updateServiceLocationData = await import( + './mutation.updateServiceLocationData.handler' + ).then((mod) => mod.updateServiceLocationData) + if (!HandlerCache.updateServiceLocationData) throw new Error('Failed to load handler') + return HandlerCache.updateServiceLocationData({ ctx, input }) }), }) diff --git a/packages/api/router/quicklink/mutation.updateEmailData.handler.ts b/packages/api/router/quicklink/mutation.updateEmailData.handler.ts new file mode 100644 index 0000000000..8fbf539023 --- /dev/null +++ b/packages/api/router/quicklink/mutation.updateEmailData.handler.ts @@ -0,0 +1,45 @@ +import flush from 'just-flush' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUpdateEmailDataSchema } from './mutation.updateEmailData.schema' + +export const updateEmailData = async ({ + ctx, + input, +}: TRPCHandlerParams) => { + try { + const updates = input.map(({ id, from, to }) => { + const { serviceOnly, locationOnly, locations, services, published } = to + const auditLogs = CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from, to: flush(to) }) + return prisma.orgEmail.update({ + where: { id }, + data: { + serviceOnly, + locationOnly, + published, + locations: { + createMany: locations.add?.length + ? { data: locations.add.map((orgLocationId) => ({ orgLocationId })), skipDuplicates: true } + : undefined, + deleteMany: locations.del?.length ? { orgLocationId: { in: locations.del } } : undefined, + }, + services: { + createMany: services.add?.length + ? { data: services.add.map((serviceId) => ({ serviceId })), skipDuplicates: true } + : undefined, + deleteMany: services.del?.length ? { serviceId: { in: services.del } } : undefined, + }, + auditLogs, + }, + }) + }) + const results = await prisma.$transaction(updates) + return results + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/quicklink/mutation.updateEmailData.schema.ts b/packages/api/router/quicklink/mutation.updateEmailData.schema.ts new file mode 100644 index 0000000000..a8c9e2009c --- /dev/null +++ b/packages/api/router/quicklink/mutation.updateEmailData.schema.ts @@ -0,0 +1,30 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUpdateEmailDataSchema = z + .object({ + id: z.string(), + from: z + .object({ + serviceOnly: z.boolean().optional(), + locationOnly: z.boolean().optional(), + published: z.boolean().optional(), + locations: z.string().array(), + services: z.string().array(), + }) + .partial(), + to: z.object({ + serviceOnly: z.boolean().optional(), + locationOnly: z.boolean().optional(), + published: z.boolean().optional(), + locations: z + .object({ add: prefixedId('orgLocation').array(), del: prefixedId('orgLocation').array() }) + .partial(), + services: z + .object({ add: prefixedId('orgService').array(), del: prefixedId('orgService').array() }) + .partial(), + }), + }) + .array() +export type TUpdateEmailDataSchema = z.infer diff --git a/packages/api/router/quicklink/mutation.updatePhoneData.handler.ts b/packages/api/router/quicklink/mutation.updatePhoneData.handler.ts new file mode 100644 index 0000000000..a7a582eb82 --- /dev/null +++ b/packages/api/router/quicklink/mutation.updatePhoneData.handler.ts @@ -0,0 +1,45 @@ +import flush from 'just-flush' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUpdatePhoneDataSchema } from './mutation.updatePhoneData.schema' + +export const updatePhoneData = async ({ + ctx, + input, +}: TRPCHandlerParams) => { + try { + const updates = input.map(({ id, from, to }) => { + const { serviceOnly, locationOnly, locations, services, published } = to + const auditLogs = CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from, to: flush(to) }) + return prisma.orgPhone.update({ + where: { id }, + data: { + serviceOnly, + locationOnly, + published, + locations: { + createMany: locations.add?.length + ? { data: locations.add.map((orgLocationId) => ({ orgLocationId })), skipDuplicates: true } + : undefined, + deleteMany: locations.del?.length ? { orgLocationId: { in: locations.del } } : undefined, + }, + services: { + createMany: services.add?.length + ? { data: services.add.map((serviceId) => ({ serviceId })), skipDuplicates: true } + : undefined, + deleteMany: services.del?.length ? { serviceId: { in: services.del } } : undefined, + }, + auditLogs, + }, + }) + }) + const results = await prisma.$transaction(updates) + return results + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/quicklink/mutation.updatePhoneData.schema.ts b/packages/api/router/quicklink/mutation.updatePhoneData.schema.ts new file mode 100644 index 0000000000..5cae1cf7aa --- /dev/null +++ b/packages/api/router/quicklink/mutation.updatePhoneData.schema.ts @@ -0,0 +1,30 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUpdatePhoneDataSchema = z + .object({ + id: z.string(), + from: z + .object({ + serviceOnly: z.boolean().optional(), + locationOnly: z.boolean().optional(), + locations: z.string().array(), + services: z.string().array(), + published: z.boolean().optional(), + }) + .partial(), + to: z.object({ + serviceOnly: z.boolean().optional(), + locationOnly: z.boolean().optional(), + locations: z + .object({ add: prefixedId('orgLocation').array(), del: prefixedId('orgLocation').array() }) + .partial(), + services: z + .object({ add: prefixedId('orgService').array(), del: prefixedId('orgService').array() }) + .partial(), + published: z.boolean().optional(), + }), + }) + .array() +export type TUpdatePhoneDataSchema = z.infer diff --git a/packages/api/router/quicklink/mutation.updateServiceLocationData.handler.ts b/packages/api/router/quicklink/mutation.updateServiceLocationData.handler.ts new file mode 100644 index 0000000000..27d9001ed0 --- /dev/null +++ b/packages/api/router/quicklink/mutation.updateServiceLocationData.handler.ts @@ -0,0 +1,37 @@ +import flush from 'just-flush' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUpdateServiceLocationDataSchema } from './mutation.updateServiceLocationData.schema' + +export const updateServiceLocationData = async ({ + ctx, + input, +}: TRPCHandlerParams) => { + try { + const updates = input.map(({ id, from, to }) => { + const { services, published } = to + const auditLogs = CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from, to: flush(to) }) + return prisma.orgLocation.update({ + where: { id }, + data: { + published, + services: { + createMany: services.add?.length + ? { data: services.add.map((serviceId) => ({ serviceId })), skipDuplicates: true } + : undefined, + deleteMany: services.del?.length ? { serviceId: { in: services.del } } : undefined, + }, + auditLogs, + }, + }) + }) + const results = await prisma.$transaction(updates) + return results + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/quicklink/mutation.updateServiceLocationData.schema.ts b/packages/api/router/quicklink/mutation.updateServiceLocationData.schema.ts new file mode 100644 index 0000000000..b707b6dcdb --- /dev/null +++ b/packages/api/router/quicklink/mutation.updateServiceLocationData.schema.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUpdateServiceLocationDataSchema = z + .object({ + id: z.string(), + from: z + .object({ + services: z.string().array(), + published: z.boolean().optional(), + }) + .partial(), + to: z.object({ + services: z + .object({ add: prefixedId('orgService').array(), del: prefixedId('orgService').array() }) + .partial(), + published: z.boolean().optional(), + }), + }) + .array() +export type TUpdateServiceLocationDataSchema = z.infer diff --git a/packages/api/router/quicklink/query.getEmailData.handler.ts b/packages/api/router/quicklink/query.getEmailData.handler.ts new file mode 100644 index 0000000000..29c840d0f4 --- /dev/null +++ b/packages/api/router/quicklink/query.getEmailData.handler.ts @@ -0,0 +1,89 @@ +import { type Prisma, prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetEmailDataSchema } from './query.getEmailData.schema' + +export const getEmailData = async ({ input }: TRPCHandlerParams) => { + try { + const limit = input.limit ?? 20 + const { skip } = input + + const where = { + published: true, + deleted: false, + emails: { + some: { + email: { AND: [{ locations: { none: {} } }, { services: { none: {} } }, { published: true }] }, + }, + }, + } satisfies Prisma.OrganizationWhereInput + + const data = await prisma.organization.findMany({ + where, + select: { + id: true, + name: true, + slug: true, + locations: { select: { id: true, name: true } }, + services: { + select: { + id: true, + serviceName: { select: { tsKey: { select: { text: true } } } }, + }, + }, + emails: { + select: { + email: { + select: { + id: true, + email: true, + firstName: true, + lastName: true, + locationOnly: true, + serviceOnly: true, + published: true, + description: { select: { tsKey: { select: { text: true } } } }, + locations: { select: { location: { select: { id: true } } } }, + services: { select: { service: { select: { id: true } } } }, + }, + }, + }, + }, + }, + orderBy: { id: 'asc' }, + take: limit, // + 1, + skip, + }) + const totalResults = await prisma.organization.count({ where }) + const transformedData = data.flatMap(({ locations, emails, services, id, name, slug }) => + emails.map(({ email }) => { + const { + description, + id: emailId, + locations: attachedLocations, + services: attachedServices, + ...rest + } = email + return { + orgId: id, + name, + slug, + emailId, + ...rest, + description: description?.tsKey.text, + attachedLocations: attachedLocations.map(({ location }) => location.id), + attachedServices: attachedServices.map(({ service }) => service.id), + locations, + services, + } + }) + ) + return { + results: transformedData, + totalResults, + } + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/quicklink/query.getEmailData.schema.ts b/packages/api/router/quicklink/query.getEmailData.schema.ts new file mode 100644 index 0000000000..721347a957 --- /dev/null +++ b/packages/api/router/quicklink/query.getEmailData.schema.ts @@ -0,0 +1,4 @@ +import { z } from 'zod' + +export const ZGetEmailDataSchema = z.object({ limit: z.number(), skip: z.number().optional() }) +export type TGetEmailDataSchema = z.infer diff --git a/packages/api/router/quicklink/query.getPhoneData.handler.ts b/packages/api/router/quicklink/query.getPhoneData.handler.ts new file mode 100644 index 0000000000..939642cd70 --- /dev/null +++ b/packages/api/router/quicklink/query.getPhoneData.handler.ts @@ -0,0 +1,95 @@ +import { type Prisma, prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetPhoneDataSchema } from './query.getPhoneData.schema' + +export const getPhoneData = async ({ input }: TRPCHandlerParams) => { + try { + const limit = input.limit ?? 20 + const { skip } = input + + const where = { + published: true, + deleted: false, + phones: { + some: { + phone: { AND: [{ locations: { none: {} } }, { services: { none: {} } }, { published: true }] }, + }, + }, + } satisfies Prisma.OrganizationWhereInput + + const data = await prisma.organization.findMany({ + where, + select: { + id: true, + name: true, + slug: true, + locations: { + select: { + id: true, + name: true, + // phones: { select: { phone: { select: { id: true } } } }, + }, + }, + phones: { + select: { + phone: { + select: { + id: true, + number: true, + description: { select: { tsKey: { select: { text: true } } } }, + country: { select: { id: true, cca2: true } }, + locationOnly: true, + serviceOnly: true, + locations: { select: { location: { select: { id: true } } } }, + services: { select: { service: { select: { id: true } } } }, + published: true, + }, + }, + }, + }, + services: { + select: { + id: true, + serviceName: { select: { tsKey: { select: { text: true } } } }, + // phones: { select: { phone: { select: { id: true } } } }, + }, + }, + }, + orderBy: { id: 'asc' }, + take: limit, // + 1, + skip, + }) + const totalResults = await prisma.organization.count({ where }) + const transformedData = data.flatMap(({ locations, phones, services, id, name, slug }) => + phones.map(({ phone }) => { + const { + description, + id: phoneId, + locations: attachedLocations, + services: attachedServices, + ...rest + } = phone + return { + orgId: id, + name, + slug, + phoneId, + ...rest, + description: description?.tsKey.text, + attachedLocations: attachedLocations.map(({ location }) => location.id), + attachedServices: attachedServices.map(({ service }) => service.id), + locations, + services, + } + }) + ) + return { + results: transformedData, + totalResults, + } + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/quicklink/query.getPhoneData.schema.ts b/packages/api/router/quicklink/query.getPhoneData.schema.ts new file mode 100644 index 0000000000..da662de2f3 --- /dev/null +++ b/packages/api/router/quicklink/query.getPhoneData.schema.ts @@ -0,0 +1,4 @@ +import { z } from 'zod' + +export const ZGetPhoneDataSchema = z.object({ limit: z.number(), skip: z.number().optional() }) +export type TGetPhoneDataSchema = z.infer diff --git a/packages/api/router/quicklink/query.getServiceLocationData.handler.ts b/packages/api/router/quicklink/query.getServiceLocationData.handler.ts new file mode 100644 index 0000000000..584a54d443 --- /dev/null +++ b/packages/api/router/quicklink/query.getServiceLocationData.handler.ts @@ -0,0 +1,73 @@ +import { type Prisma, prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetServiceLocationDataSchema } from './query.getServiceLocationData.schema' + +export const getServiceLocationData = async ({ input }: TRPCHandlerParams) => { + try { + const limit = input.limit ?? 20 + const { skip } = input + + const where = { + published: true, + deleted: false, + locations: { + some: { services: { none: {} }, published: true }, + }, + } satisfies Prisma.OrganizationWhereInput + const results = await prisma.organization.findMany({ + where, + select: { + id: true, + name: true, + slug: true, + locations: { + select: { + id: true, + name: true, + published: true, + services: { + select: { + service: { + select: { id: true, serviceName: { select: { tsKey: { select: { text: true } } } } }, + }, + }, + }, + }, + where: { services: { none: {} }, published: true, deleted: false }, + }, + services: { + select: { + id: true, + serviceName: { select: { tsKey: { select: { text: true } } } }, + }, + }, + }, + orderBy: { id: 'asc' }, + take: limit, // + 1, + skip, + }) + const totalResults = await prisma.organization.count({ where }) + const transformedData = results.flatMap(({ locations, services, id, name, slug }) => + locations.map(({ id: locationId, name: locationName, services: locationServices, published }) => { + return { + orgId: id, + name, + slug, + locationId, + locationName, + published, + attachedServices: locationServices.map(({ service }) => service.id), + services, + } + }) + ) + return { + results: transformedData, + totalResults, + } + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/quicklink/query.getServiceLocationData.schema.ts b/packages/api/router/quicklink/query.getServiceLocationData.schema.ts new file mode 100644 index 0000000000..97111f8cbb --- /dev/null +++ b/packages/api/router/quicklink/query.getServiceLocationData.schema.ts @@ -0,0 +1,4 @@ +import { z } from 'zod' + +export const ZGetServiceLocationDataSchema = z.object({ limit: z.number(), skip: z.number().optional() }) +export type TGetServiceLocationDataSchema = z.infer diff --git a/packages/api/router/quicklink/schemas.ts b/packages/api/router/quicklink/schemas.ts new file mode 100644 index 0000000000..e268d50576 --- /dev/null +++ b/packages/api/router/quicklink/schemas.ts @@ -0,0 +1,8 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.updateEmailData.schema' +export * from './mutation.updatePhoneData.schema' +export * from './mutation.updateServiceLocationData.schema' +export * from './query.getEmailData.schema' +export * from './query.getPhoneData.schema' +export * from './query.getServiceLocationData.schema' +// codegen:end From 2424ed199671a628dae87996a0134e1cbf1338bc Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 20:18:27 -0400 Subject: [PATCH 33/63] review router caching/lazy load --- packages/api/lib/permissions.ts | 9 + packages/api/router/review/index.ts | 458 ++++-------------- .../router/review/mutation.create.handler.ts | 27 ++ .../router/review/mutation.create.schema.ts | 19 + .../router/review/mutation.delete.handler.ts | 32 ++ .../router/review/mutation.delete.schema.ts | 6 + .../router/review/mutation.hide.handler.ts | 32 ++ .../api/router/review/mutation.hide.schema.ts | 6 + .../review/mutation.unDelete.handler.ts | 32 ++ .../router/review/mutation.unDelete.schema.ts | 6 + .../router/review/mutation.unHide.handler.ts | 32 ++ .../router/review/mutation.unHide.schema.ts | 6 + .../router/review/query.getAverage.handler.ts | 39 ++ .../router/review/query.getAverage.schema.ts | 10 + .../router/review/query.getByIds.handler.ts | 106 ++++ .../router/review/query.getByIds.schema.ts | 6 + .../review/query.getByLocation.handler.ts | 19 + .../review/query.getByLocation.schema.ts | 9 + .../router/review/query.getByOrg.handler.ts | 18 + .../router/review/query.getByOrg.schema.ts | 6 + .../review/query.getByService.handler.ts | 19 + .../review/query.getByService.schema.ts | 9 + .../router/review/query.getByUser.handler.ts | 23 + .../router/review/query.getByUser.schema.ts | 6 + .../review/query.getCurrentUser.handler.ts | 21 + .../review/query.getFeatured.handler.ts | 118 +++++ .../router/review/query.getFeatured.schema.ts | 6 + packages/api/router/review/schemas.ts | 14 + 28 files changed, 729 insertions(+), 365 deletions(-) create mode 100644 packages/api/router/review/mutation.create.handler.ts create mode 100644 packages/api/router/review/mutation.create.schema.ts create mode 100644 packages/api/router/review/mutation.delete.handler.ts create mode 100644 packages/api/router/review/mutation.delete.schema.ts create mode 100644 packages/api/router/review/mutation.hide.handler.ts create mode 100644 packages/api/router/review/mutation.hide.schema.ts create mode 100644 packages/api/router/review/mutation.unDelete.handler.ts create mode 100644 packages/api/router/review/mutation.unDelete.schema.ts create mode 100644 packages/api/router/review/mutation.unHide.handler.ts create mode 100644 packages/api/router/review/mutation.unHide.schema.ts create mode 100644 packages/api/router/review/query.getAverage.handler.ts create mode 100644 packages/api/router/review/query.getAverage.schema.ts create mode 100644 packages/api/router/review/query.getByIds.handler.ts create mode 100644 packages/api/router/review/query.getByIds.schema.ts create mode 100644 packages/api/router/review/query.getByLocation.handler.ts create mode 100644 packages/api/router/review/query.getByLocation.schema.ts create mode 100644 packages/api/router/review/query.getByOrg.handler.ts create mode 100644 packages/api/router/review/query.getByOrg.schema.ts create mode 100644 packages/api/router/review/query.getByService.handler.ts create mode 100644 packages/api/router/review/query.getByService.schema.ts create mode 100644 packages/api/router/review/query.getByUser.handler.ts create mode 100644 packages/api/router/review/query.getByUser.schema.ts create mode 100644 packages/api/router/review/query.getCurrentUser.handler.ts create mode 100644 packages/api/router/review/query.getFeatured.handler.ts create mode 100644 packages/api/router/review/query.getFeatured.schema.ts create mode 100644 packages/api/router/review/schemas.ts diff --git a/packages/api/lib/permissions.ts b/packages/api/lib/permissions.ts index f13e80e3a6..0a661159e1 100644 --- a/packages/api/lib/permissions.ts +++ b/packages/api/lib/permissions.ts @@ -52,6 +52,14 @@ const orgWebsite = { updateOrgWebsite: 'editSingleOrg', } satisfies PermissionDefs +const reviews = { + viewUserReviews: ['viewUserReviews'], + hideUserReview: ['hideUserReview'], + unHideUserReview: ['showUserReview'], + deleteUserReview: ['deleteUserReview'], + undeleteUserReview: ['undeleteUserReview'], +} satisfies PermissionDefs + const system = { createPermission: 'adminPermissions', getDetails: ['dataPortalBasic'], @@ -67,6 +75,7 @@ const permissions = { ...orgService, ...orgSocialMedia, ...orgWebsite, + ...reviews, ...system, } satisfies PermissionDefs diff --git a/packages/api/router/review/index.ts b/packages/api/router/review/index.ts index 984b1cdb9a..b396d60a46 100644 --- a/packages/api/router/review/index.ts +++ b/packages/api/router/review/index.ts @@ -1,389 +1,117 @@ -import { z } from 'zod' - -import { handleError } from '~api/lib/errorHandler' -import { defineRouter, protectedProcedure, publicProcedure, staffProcedure } from '~api/lib/trpc' -import { id, orgId, orgIdLocationId, orgIdServiceId, userId } from '~api/schemas/common' -import { CreateReview, CreateReviewInput } from '~api/schemas/create/review' -import { ReviewToggleDelete, ReviewVisibility } from '~api/schemas/update/review' - -const getRandomItems = (items: T[], count: number): T[] => { - const randomIndexes = new Set() - if (items.length < count) - throw new Error('Count exceeds the number of items!', { cause: { items: items.length, count } }) - - while (randomIndexes.size < count) { - randomIndexes.add(Math.floor(Math.random() * items.length)) - } - - return [...randomIndexes].map((i) => items[i]) as T[] +import { defineRouter, permissionedProcedure, protectedProcedure, publicProcedure } from '~api/lib/trpc' + +import * as schema from './schemas' + +const HandlerCache: Partial = {} + +type ReviewHandlerCache = { + create: typeof import('./mutation.create.handler').create + getCurrentUser: typeof import('./query.getCurrentUser.handler').getCurrentUser + getByOrg: typeof import('./query.getByOrg.handler').getByOrg + getByLocation: typeof import('./query.getByLocation.handler').getByLocation + getByService: typeof import('./query.getByService.handler').getByService + getByIds: typeof import('./query.getByIds.handler').getByIds + getByUser: typeof import('./query.getByUser.handler').getByUser + getAverage: typeof import('./query.getAverage.handler').getAverage + getFeatured: typeof import('./query.getFeatured.handler').getFeatured + hide: typeof import('./mutation.hide.handler').hide + unHide: typeof import('./mutation.unHide.handler').unHide + delete: typeof import('./mutation.delete.handler').deleteReview + unDelete: typeof import('./mutation.unDelete.handler').unDelete } export const reviewRouter = defineRouter({ - create: protectedProcedure.input(z.object(CreateReviewInput)).mutation(async ({ ctx, input }) => { - try { - const { data } = CreateReview.parse({ ...input, userId: ctx.session.user.id }) - - const review = await ctx.prisma.orgReview.create({ data, select: { id: true } }) - - return review - } catch (error) { - handleError(error) - } + create: protectedProcedure.input(schema.ZCreateSchema).mutation(async ({ ctx, input }) => { + if (!HandlerCache.create) + HandlerCache.create = await import('./mutation.create.handler').then((mod) => mod.create) + if (!HandlerCache.create) throw new Error('Failed to load handler') + return HandlerCache.create({ ctx, input }) }), getCurrentUser: protectedProcedure.query(async ({ ctx }) => { - try { - const reviews = await ctx.prisma.orgReview.findMany({ - where: { - userId: ctx.session.user.id, - }, - include: { - organization: true, - orgLocation: true, - orgService: true, - }, - }) - return reviews - } catch (error) { - handleError(error) - } + if (!HandlerCache.getCurrentUser) + HandlerCache.getCurrentUser = await import('./query.getCurrentUser.handler').then( + (mod) => mod.getCurrentUser + ) + if (!HandlerCache.getCurrentUser) throw new Error('Failed to load handler') + return HandlerCache.getCurrentUser({ ctx }) }), - getByOrg: publicProcedure.input(orgId).query(async ({ ctx, input }) => { - try { - const reviews = await ctx.prisma.orgReview.findMany({ - where: { - organizationId: input.orgId, - }, - }) - return reviews - } catch (error) { - handleError(error) - } + getByOrg: publicProcedure.input(schema.ZGetByOrgSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getByOrg) + HandlerCache.getByOrg = await import('./query.getByOrg.handler').then((mod) => mod.getByOrg) + if (!HandlerCache.getByOrg) throw new Error('Failed to load handler') + return HandlerCache.getByOrg({ ctx, input }) }), - getByLocation: publicProcedure.input(orgIdLocationId).query(async ({ ctx, input }) => { - try { - const reviews = await ctx.prisma.orgReview.findMany({ - where: { - organizationId: input.orgId, - orgLocationId: input.locationId, - }, - }) - return reviews - } catch (error) { - handleError(error) - } + getByLocation: publicProcedure.input(schema.ZGetByLocationSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getByLocation) + HandlerCache.getByLocation = await import('./query.getByLocation.handler').then( + (mod) => mod.getByLocation + ) + if (!HandlerCache.getByLocation) throw new Error('Failed to load handler') + return HandlerCache.getByLocation({ ctx, input }) }), - getByService: publicProcedure.input(orgIdServiceId).query(async ({ ctx, input }) => { - try { - const reviews = await ctx.prisma.orgReview.findMany({ - where: { - organizationId: input.orgId, - orgServiceId: input.serviceId, - }, - }) - return reviews - } catch (error) { - handleError(error) - } + getByService: publicProcedure.input(schema.ZGetByServiceSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getByService) + HandlerCache.getByService = await import('./query.getByService.handler').then((mod) => mod.getByService) + if (!HandlerCache.getByService) throw new Error('Failed to load handler') + return HandlerCache.getByService({ ctx, input }) }), /** Returns user reviews ready for public display. Takes reviewer's privacy preferences in to account */ - getByIds: publicProcedure.input(z.string().array()).query(async ({ ctx, input }) => { - const results = await ctx.prisma.orgReview.findMany({ - where: { - id: { - in: input, - }, - visible: true, - deleted: false, - }, - select: { - id: true, - rating: true, - reviewText: true, - user: { - select: { - name: true, - image: true, - fieldVisibility: { - select: { - name: true, - image: true, - currentCity: true, - currentGovDist: true, - currentCountry: true, - }, - }, - permissions: { - where: { - permission: { - name: 'isLCR', - }, - }, - }, - }, - }, - language: { - select: { - languageName: true, - nativeName: true, - }, - }, - langConfidence: true, - translatedText: { - select: { - text: true, - language: { - select: { localeCode: true }, - }, - }, - }, - lcrCity: true, - lcrGovDist: { - select: { - tsKey: true, - tsNs: true, - }, - }, - lcrCountry: { - select: { - tsNs: true, - tsKey: true, - }, - }, - createdAt: true, - }, - }) - - const filteredResults = results.map((result) => { - const { - name: nameVisible, - image: imageVisible, - currentCity: cityVisible, - currentGovDist: distVisible, - currentCountry: countryVisible, - } = result.user.fieldVisibility ?? { - name: false, - image: false, - currentCity: false, - currentGovDist: false, - currentCountry: false, - } - - return { - ...result, - user: { - image: imageVisible ? result.user.image : null, - name: nameVisible ? result.user.name : null, - }, - lcrCity: cityVisible ? result.lcrCity : null, - lcrGovDist: distVisible ? result.lcrGovDist : null, - lcrCountry: countryVisible ? result.lcrCountry : null, - verifiedUser: Boolean(result.user.permissions.length), - } - }) - return filteredResults + getByIds: publicProcedure.input(schema.ZGetByIdsSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getByIds) + HandlerCache.getByIds = await import('./query.getByIds.handler').then((mod) => mod.getByIds) + if (!HandlerCache.getByIds) throw new Error('Failed to load handler') + return HandlerCache.getByIds({ ctx, input }) }), - getByUser: staffProcedure - .input(userId) - .meta({ hasPerm: 'viewUserReviews' }) + getByUser: permissionedProcedure('viewUserReviews') + .input(schema.ZGetByUserSchema) .query(async ({ ctx, input }) => { - try { - const reviews = await ctx.prisma.orgReview.findMany({ - where: { - userId: input.userId, - }, - include: { - organization: true, - orgLocation: true, - orgService: true, - }, - }) - return reviews - } catch (error) { - handleError(error) - } + if (!HandlerCache.getByUser) + HandlerCache.getByUser = await import('./query.getByUser.handler').then((mod) => mod.getByUser) + if (!HandlerCache.getByUser) throw new Error('Failed to load handler') + return HandlerCache.getByUser({ ctx, input }) }), - hide: staffProcedure - .input(id) - .meta({ hasPerm: 'hideUserReview' }) + hide: permissionedProcedure('hideUserReview') + .input(schema.ZHideSchema) .mutation(async ({ ctx, input }) => { - try { - const inputData = { - ...input, - actorId: ctx.session.user.id, - visible: false, - } satisfies z.input - - const data = ReviewVisibility.parse(inputData) - - const result = await ctx.prisma.orgReview.update(data) - return result - } catch (error) { - handleError(error) - } + if (!HandlerCache.hide) + HandlerCache.hide = await import('./mutation.hide.handler').then((mod) => mod.hide) + if (!HandlerCache.hide) throw new Error('Failed to load handler') + return HandlerCache.hide({ ctx, input }) }), - unHide: staffProcedure - .input(id) - .meta({ hasPerm: 'showUserReview' }) + unHide: permissionedProcedure('unHideUserReview') + .input(schema.ZUnHideSchema) .mutation(async ({ ctx, input }) => { - try { - const inputData = { - ...input, - actorId: ctx.session.user.id, - visible: true, - } satisfies z.input - - const data = ReviewVisibility.parse(inputData) - - const result = await ctx.prisma.orgReview.update(data) - return result - } catch (error) { - handleError(error) - } + if (!HandlerCache.unHide) + HandlerCache.unHide = await import('./mutation.unHide.handler').then((mod) => mod.unHide) + if (!HandlerCache.unHide) throw new Error('Failed to load handler') + return HandlerCache.unHide({ ctx, input }) }), - delete: protectedProcedure - .input(id) - .meta({ hasPerm: 'deleteUserReview' }) + delete: permissionedProcedure('deleteUserReview') + .input(schema.ZDeleteSchema) .mutation(async ({ ctx, input }) => { - try { - const inputData = { - ...input, - actorId: ctx.session.user.id, - deleted: true, - } satisfies z.input - const data = ReviewToggleDelete.parse(inputData) - - const result = await ctx.prisma.orgReview.update(data) - return result - } catch (error) { - handleError(error) - } + if (!HandlerCache.delete) + HandlerCache.delete = await import('./mutation.delete.handler').then((mod) => mod.deleteReview) + if (!HandlerCache.delete) throw new Error('Failed to load handler') + return HandlerCache.delete({ ctx, input }) }), - unDelete: protectedProcedure - .input(id) - .meta({ hasPerm: 'deleteUserReview' }) + unDelete: permissionedProcedure('undeleteUserReview') + .input(schema.ZUnDeleteSchema) .mutation(async ({ ctx, input }) => { - try { - const inputData = { - ...input, - actorId: ctx.session.user.id, - deleted: false, - } satisfies z.input - const data = ReviewToggleDelete.parse(inputData) - - const result = await ctx.prisma.orgReview.update(data) - return result - } catch (error) { - handleError(error) - } + if (!HandlerCache.unDelete) + HandlerCache.unDelete = await import('./mutation.unDelete.handler').then((mod) => mod.unDelete) + if (!HandlerCache.unDelete) throw new Error('Failed to load handler') + return HandlerCache.unDelete({ ctx, input }) }), - getAverage: publicProcedure.input(z.string()).query(async ({ ctx, input }) => { - const result = await ctx.prisma.orgReview.aggregate({ - _avg: { - rating: true, - }, - _count: { - rating: true, - }, - where: { - OR: [{ orgLocationId: input }, { orgServiceId: input }, { organizationId: input }], - }, - }) - return { average: result._avg.rating, count: result._count.rating } + getAverage: publicProcedure.input(schema.ZGetAverageSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getAverage) + HandlerCache.getAverage = await import('./query.getAverage.handler').then((mod) => mod.getAverage) + if (!HandlerCache.getAverage) throw new Error('Failed to load handler') + return HandlerCache.getAverage({ ctx, input }) }), - getFeatured: publicProcedure.input(z.number()).query(async ({ ctx, input }) => { - const results = await ctx.prisma.orgReview.findMany({ - where: { - featured: true, - visible: true, - deleted: false, - }, - select: { - id: true, - rating: true, - reviewText: true, - user: { - select: { - name: true, - image: true, - fieldVisibility: { - select: { - name: true, - image: true, - currentCity: true, - currentGovDist: true, - currentCountry: true, - }, - }, - permissions: { - where: { - permission: { - name: 'isLCR', - }, - }, - }, - }, - }, - language: { - select: { - languageName: true, - nativeName: true, - }, - }, - langConfidence: true, - translatedText: { - select: { - text: true, - language: { - select: { localeCode: true }, - }, - }, - }, - lcrCity: true, - lcrGovDist: { - select: { - tsKey: true, - tsNs: true, - }, - }, - lcrCountry: { - select: { - tsNs: true, - tsKey: true, - }, - }, - createdAt: true, - }, - }) - - const randomResults = getRandomItems(results, input) - - const filteredResults = randomResults.map((result) => { - const { - name: nameVisible, - image: imageVisible, - currentCity: cityVisible, - currentGovDist: distVisible, - currentCountry: countryVisible, - } = result.user.fieldVisibility ?? { - name: false, - image: false, - currentCity: false, - currentGovDist: false, - currentCountry: false, - } - - return { - ...result, - user: { - image: imageVisible ? result.user.image : null, - name: nameVisible ? result.user.name : null, - }, - lcrCity: cityVisible ? result.lcrCity : null, - lcrGovDist: distVisible ? result.lcrGovDist : null, - lcrCountry: countryVisible ? result.lcrCountry : null, - verifiedUser: Boolean(result.user.permissions.length), - } - }) - return filteredResults + getFeatured: publicProcedure.input(schema.ZGetFeaturedSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getFeatured) + HandlerCache.getFeatured = await import('./query.getFeatured.handler').then((mod) => mod.getFeatured) + if (!HandlerCache.getFeatured) throw new Error('Failed to load handler') + return HandlerCache.getFeatured({ ctx, input }) }), }) diff --git a/packages/api/router/review/mutation.create.handler.ts b/packages/api/router/review/mutation.create.handler.ts new file mode 100644 index 0000000000..1d12af0bac --- /dev/null +++ b/packages/api/router/review/mutation.create.handler.ts @@ -0,0 +1,27 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateSchema } from './mutation.create.schema' + +export const create = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const review = await prisma.orgReview.create({ + data: { + ...input, + userId: ctx.session.user.id, + auditLogs: CreateAuditLog({ + actorId: ctx.actorId, + operation: 'CREATE', + to: { ...input, userId: ctx.session.user.id }, + }), + }, + select: { id: true }, + }) + + return review + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/review/mutation.create.schema.ts b/packages/api/router/review/mutation.create.schema.ts new file mode 100644 index 0000000000..39b8ee58a2 --- /dev/null +++ b/packages/api/router/review/mutation.create.schema.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZCreateSchema = z.object({ + rating: z.number(), + reviewText: z.string().optional(), + visible: z.boolean().optional(), + toxicity: z.number().optional(), + lcrCity: z.string().optional(), + langConfidence: z.number().optional(), + organizationId: prefixedId('organization'), + orgServiceId: prefixedId('orgService').optional(), + orgLocationId: prefixedId('orgLocation').optional(), + languageId: prefixedId('language').optional(), + lcrGovDistId: prefixedId('govDist').optional(), + lcrCountryId: prefixedId('country').optional(), +}) +export type TCreateSchema = z.infer diff --git a/packages/api/router/review/mutation.delete.handler.ts b/packages/api/router/review/mutation.delete.handler.ts new file mode 100644 index 0000000000..0c5e1ddd8d --- /dev/null +++ b/packages/api/router/review/mutation.delete.handler.ts @@ -0,0 +1,32 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TDeleteSchema } from './mutation.delete.schema' + +export const deleteReview = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const deleted = true + + const result = await prisma.orgReview.update({ + where: { id: input.id }, + data: { + deleted, + auditLogs: CreateAuditLog({ + actorId: ctx.actorId, + operation: 'UPDATE', + from: { deleted: !deleted }, + to: { deleted: deleted }, + }), + }, + select: { + id: true, + deleted: true, + }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/review/mutation.delete.schema.ts b/packages/api/router/review/mutation.delete.schema.ts new file mode 100644 index 0000000000..4bbc0e7547 --- /dev/null +++ b/packages/api/router/review/mutation.delete.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZDeleteSchema = z.object({ id: prefixedId('orgReview') }) +export type TDeleteSchema = z.infer diff --git a/packages/api/router/review/mutation.hide.handler.ts b/packages/api/router/review/mutation.hide.handler.ts new file mode 100644 index 0000000000..c356adb3f0 --- /dev/null +++ b/packages/api/router/review/mutation.hide.handler.ts @@ -0,0 +1,32 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type THideSchema } from './mutation.hide.schema' + +export const hide = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const visible = false + + const result = await prisma.orgReview.update({ + where: { id: input.id }, + data: { + visible, + auditLogs: CreateAuditLog({ + actorId: ctx.actorId, + operation: 'UPDATE', + from: { visible: !visible }, + to: { visible }, + }), + }, + select: { + id: true, + visible: true, + }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/review/mutation.hide.schema.ts b/packages/api/router/review/mutation.hide.schema.ts new file mode 100644 index 0000000000..1570c7bc0c --- /dev/null +++ b/packages/api/router/review/mutation.hide.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZHideSchema = z.object({ id: prefixedId('orgReview') }) +export type THideSchema = z.infer diff --git a/packages/api/router/review/mutation.unDelete.handler.ts b/packages/api/router/review/mutation.unDelete.handler.ts new file mode 100644 index 0000000000..e2a7800ea0 --- /dev/null +++ b/packages/api/router/review/mutation.unDelete.handler.ts @@ -0,0 +1,32 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUnDeleteSchema } from './mutation.unDelete.schema' + +export const unDelete = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const deleted = false + + const result = await prisma.orgReview.update({ + where: { id: input.id }, + data: { + deleted, + auditLogs: CreateAuditLog({ + actorId: ctx.actorId, + operation: 'UPDATE', + from: { deleted: !deleted }, + to: { deleted: deleted }, + }), + }, + select: { + id: true, + deleted: true, + }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/review/mutation.unDelete.schema.ts b/packages/api/router/review/mutation.unDelete.schema.ts new file mode 100644 index 0000000000..156b4316ab --- /dev/null +++ b/packages/api/router/review/mutation.unDelete.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUnDeleteSchema = z.object({ id: prefixedId('orgReview') }) +export type TUnDeleteSchema = z.infer diff --git a/packages/api/router/review/mutation.unHide.handler.ts b/packages/api/router/review/mutation.unHide.handler.ts new file mode 100644 index 0000000000..25dca49ce9 --- /dev/null +++ b/packages/api/router/review/mutation.unHide.handler.ts @@ -0,0 +1,32 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUnHideSchema } from './mutation.unHide.schema' + +export const unHide = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const visible = true + + const result = await prisma.orgReview.update({ + where: { id: input.id }, + data: { + visible, + auditLogs: CreateAuditLog({ + actorId: ctx.actorId, + operation: 'UPDATE', + from: { visible: !visible }, + to: { visible }, + }), + }, + select: { + id: true, + visible: true, + }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/review/mutation.unHide.schema.ts b/packages/api/router/review/mutation.unHide.schema.ts new file mode 100644 index 0000000000..17f8e99457 --- /dev/null +++ b/packages/api/router/review/mutation.unHide.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUnHideSchema = z.object({ id: prefixedId('orgReview') }) +export type TUnHideSchema = z.infer diff --git a/packages/api/router/review/query.getAverage.handler.ts b/packages/api/router/review/query.getAverage.handler.ts new file mode 100644 index 0000000000..37334e64d7 --- /dev/null +++ b/packages/api/router/review/query.getAverage.handler.ts @@ -0,0 +1,39 @@ +import { isIdFor, type Prisma, prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetAverageSchema } from './query.getAverage.schema' + +export const getAverage = async ({ input }: TRPCHandlerParams) => { + try { + const whereId = (): Prisma.OrgReviewWhereInput => { + switch (true) { + case isIdFor('organization', input): { + return { organizationId: input } + } + case isIdFor('orgLocation', input): { + return { orgLocationId: input } + } + case isIdFor('orgService', input): { + return { orgServiceId: input } + } + default: { + return {} + } + } + } + + const result = await prisma.orgReview.aggregate({ + _avg: { + rating: true, + }, + _count: { + rating: true, + }, + where: whereId(), + }) + return { average: result._avg.rating, count: result._count.rating } + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/review/query.getAverage.schema.ts b/packages/api/router/review/query.getAverage.schema.ts new file mode 100644 index 0000000000..c2db6e5555 --- /dev/null +++ b/packages/api/router/review/query.getAverage.schema.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetAverageSchema = z.union([ + prefixedId('organization'), + prefixedId('orgService'), + prefixedId('orgLocation'), +]) +export type TGetAverageSchema = z.infer diff --git a/packages/api/router/review/query.getByIds.handler.ts b/packages/api/router/review/query.getByIds.handler.ts new file mode 100644 index 0000000000..c8cf297b80 --- /dev/null +++ b/packages/api/router/review/query.getByIds.handler.ts @@ -0,0 +1,106 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetByIdsSchema } from './query.getByIds.schema' + +export const getByIds = async ({ input }: TRPCHandlerParams) => { + try { + const results = await prisma.orgReview.findMany({ + where: { + id: { + in: input, + }, + visible: true, + deleted: false, + }, + select: { + id: true, + rating: true, + reviewText: true, + user: { + select: { + name: true, + image: true, + fieldVisibility: { + select: { + name: true, + image: true, + currentCity: true, + currentGovDist: true, + currentCountry: true, + }, + }, + permissions: { + where: { + permission: { + name: 'isLCR', + }, + }, + }, + }, + }, + language: { + select: { + languageName: true, + nativeName: true, + }, + }, + langConfidence: true, + translatedText: { + select: { + text: true, + language: { + select: { localeCode: true }, + }, + }, + }, + lcrCity: true, + lcrGovDist: { + select: { + tsKey: true, + tsNs: true, + }, + }, + lcrCountry: { + select: { + tsNs: true, + tsKey: true, + }, + }, + createdAt: true, + }, + }) + + const filteredResults = results.map((result) => { + const { + name: nameVisible, + image: imageVisible, + currentCity: cityVisible, + currentGovDist: distVisible, + currentCountry: countryVisible, + } = result.user.fieldVisibility ?? { + name: false, + image: false, + currentCity: false, + currentGovDist: false, + currentCountry: false, + } + + return { + ...result, + user: { + image: imageVisible ? result.user.image : null, + name: nameVisible ? result.user.name : null, + }, + lcrCity: cityVisible ? result.lcrCity : null, + lcrGovDist: distVisible ? result.lcrGovDist : null, + lcrCountry: countryVisible ? result.lcrCountry : null, + verifiedUser: Boolean(result.user.permissions.length), + } + }) + return filteredResults + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/review/query.getByIds.schema.ts b/packages/api/router/review/query.getByIds.schema.ts new file mode 100644 index 0000000000..b98b7c11e2 --- /dev/null +++ b/packages/api/router/review/query.getByIds.schema.ts @@ -0,0 +1,6 @@ +import { type z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetByIdsSchema = prefixedId('orgReview').array() +export type TGetByIdsSchema = z.infer diff --git a/packages/api/router/review/query.getByLocation.handler.ts b/packages/api/router/review/query.getByLocation.handler.ts new file mode 100644 index 0000000000..1d50a4b869 --- /dev/null +++ b/packages/api/router/review/query.getByLocation.handler.ts @@ -0,0 +1,19 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetByLocationSchema } from './query.getByLocation.schema' + +export const getByLocation = async ({ input }: TRPCHandlerParams) => { + try { + const reviews = await prisma.orgReview.findMany({ + where: { + organizationId: input.orgId, + orgLocationId: input.locationId, + }, + }) + return reviews + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/review/query.getByLocation.schema.ts b/packages/api/router/review/query.getByLocation.schema.ts new file mode 100644 index 0000000000..309fd672e8 --- /dev/null +++ b/packages/api/router/review/query.getByLocation.schema.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetByLocationSchema = z.object({ + orgId: prefixedId('organization'), + locationId: prefixedId('orgLocation'), +}) +export type TGetByLocationSchema = z.infer diff --git a/packages/api/router/review/query.getByOrg.handler.ts b/packages/api/router/review/query.getByOrg.handler.ts new file mode 100644 index 0000000000..a7c1ebc788 --- /dev/null +++ b/packages/api/router/review/query.getByOrg.handler.ts @@ -0,0 +1,18 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetByOrgSchema } from './query.getByOrg.schema' + +export const getByOrg = async ({ input }: TRPCHandlerParams) => { + try { + const reviews = await prisma.orgReview.findMany({ + where: { + organizationId: input.orgId, + }, + }) + return reviews + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/review/query.getByOrg.schema.ts b/packages/api/router/review/query.getByOrg.schema.ts new file mode 100644 index 0000000000..0d8100d716 --- /dev/null +++ b/packages/api/router/review/query.getByOrg.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetByOrgSchema = z.object({ orgId: prefixedId('organization') }) +export type TGetByOrgSchema = z.infer diff --git a/packages/api/router/review/query.getByService.handler.ts b/packages/api/router/review/query.getByService.handler.ts new file mode 100644 index 0000000000..92d94d654d --- /dev/null +++ b/packages/api/router/review/query.getByService.handler.ts @@ -0,0 +1,19 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetByServiceSchema } from './query.getByService.schema' + +export const getByService = async ({ input }: TRPCHandlerParams) => { + try { + const reviews = await prisma.orgReview.findMany({ + where: { + organizationId: input.orgId, + orgServiceId: input.serviceId, + }, + }) + return reviews + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/review/query.getByService.schema.ts b/packages/api/router/review/query.getByService.schema.ts new file mode 100644 index 0000000000..7773603dea --- /dev/null +++ b/packages/api/router/review/query.getByService.schema.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetByServiceSchema = z.object({ + orgId: prefixedId('organization'), + serviceId: prefixedId('orgService'), +}) +export type TGetByServiceSchema = z.infer diff --git a/packages/api/router/review/query.getByUser.handler.ts b/packages/api/router/review/query.getByUser.handler.ts new file mode 100644 index 0000000000..3ff6a13988 --- /dev/null +++ b/packages/api/router/review/query.getByUser.handler.ts @@ -0,0 +1,23 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetByUserSchema } from './query.getByUser.schema' + +export const getByUser = async ({ input }: TRPCHandlerParams) => { + try { + const reviews = await prisma.orgReview.findMany({ + where: { + userId: input.userId, + }, + include: { + organization: true, + orgLocation: true, + orgService: true, + }, + }) + return reviews + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/review/query.getByUser.schema.ts b/packages/api/router/review/query.getByUser.schema.ts new file mode 100644 index 0000000000..504a1eafe9 --- /dev/null +++ b/packages/api/router/review/query.getByUser.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetByUserSchema = z.object({ userId: prefixedId('user') }) +export type TGetByUserSchema = z.infer diff --git a/packages/api/router/review/query.getCurrentUser.handler.ts b/packages/api/router/review/query.getCurrentUser.handler.ts new file mode 100644 index 0000000000..9e8f314f18 --- /dev/null +++ b/packages/api/router/review/query.getCurrentUser.handler.ts @@ -0,0 +1,21 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +export const getCurrentUser = async ({ ctx }: TRPCHandlerParams) => { + try { + const reviews = await prisma.orgReview.findMany({ + where: { + userId: ctx.session.user.id, + }, + include: { + organization: true, + orgLocation: true, + orgService: true, + }, + }) + return reviews + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/review/query.getFeatured.handler.ts b/packages/api/router/review/query.getFeatured.handler.ts new file mode 100644 index 0000000000..4c8e8c1b20 --- /dev/null +++ b/packages/api/router/review/query.getFeatured.handler.ts @@ -0,0 +1,118 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetFeaturedSchema } from './query.getFeatured.schema' + +const getRandomItems = (items: T[], count: number): T[] => { + const randomIndexes = new Set() + if (items.length < count) + throw new Error('Count exceeds the number of items!', { cause: { items: items.length, count } }) + + while (randomIndexes.size < count) { + randomIndexes.add(Math.floor(Math.random() * items.length)) + } + + return [...randomIndexes].map((i) => items[i]) as T[] +} + +export const getFeatured = async ({ input }: TRPCHandlerParams) => { + try { + const results = await prisma.orgReview.findMany({ + where: { + featured: true, + visible: true, + deleted: false, + }, + select: { + id: true, + rating: true, + reviewText: true, + user: { + select: { + name: true, + image: true, + fieldVisibility: { + select: { + name: true, + image: true, + currentCity: true, + currentGovDist: true, + currentCountry: true, + }, + }, + permissions: { + where: { + permission: { + name: 'isLCR', + }, + }, + }, + }, + }, + language: { + select: { + languageName: true, + nativeName: true, + }, + }, + langConfidence: true, + translatedText: { + select: { + text: true, + language: { + select: { localeCode: true }, + }, + }, + }, + lcrCity: true, + lcrGovDist: { + select: { + tsKey: true, + tsNs: true, + }, + }, + lcrCountry: { + select: { + tsNs: true, + tsKey: true, + }, + }, + createdAt: true, + }, + }) + + const randomResults = getRandomItems(results, input) + + const filteredResults = randomResults.map((result) => { + const { + name: nameVisible, + image: imageVisible, + currentCity: cityVisible, + currentGovDist: distVisible, + currentCountry: countryVisible, + } = result.user.fieldVisibility ?? { + name: false, + image: false, + currentCity: false, + currentGovDist: false, + currentCountry: false, + } + + return { + ...result, + user: { + image: imageVisible ? result.user.image : null, + name: nameVisible ? result.user.name : null, + }, + lcrCity: cityVisible ? result.lcrCity : null, + lcrGovDist: distVisible ? result.lcrGovDist : null, + lcrCountry: countryVisible ? result.lcrCountry : null, + verifiedUser: Boolean(result.user.permissions.length), + } + }) + return filteredResults + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/review/query.getFeatured.schema.ts b/packages/api/router/review/query.getFeatured.schema.ts new file mode 100644 index 0000000000..54d2afb3c6 --- /dev/null +++ b/packages/api/router/review/query.getFeatured.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetFeaturedSchema = z.number() +export type TGetFeaturedSchema = z.infer diff --git a/packages/api/router/review/schemas.ts b/packages/api/router/review/schemas.ts new file mode 100644 index 0000000000..53ad8b0a9b --- /dev/null +++ b/packages/api/router/review/schemas.ts @@ -0,0 +1,14 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.create.schema' +export * from './mutation.delete.schema' +export * from './mutation.hide.schema' +export * from './mutation.unDelete.schema' +export * from './mutation.unHide.schema' +export * from './query.getAverage.schema' +export * from './query.getByIds.schema' +export * from './query.getByLocation.schema' +export * from './query.getByOrg.schema' +export * from './query.getByService.schema' +export * from './query.getByUser.schema' +export * from './query.getFeatured.schema' +// codegen:end From a8b04aec89290614c20fb48d3e36afb2209f340b Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Mon, 7 Aug 2023 20:20:50 -0400 Subject: [PATCH 34/63] data migration: add permissions --- packages/db/generated/permission.ts | 1 + .../data-migrations/2023-08-07_permissions.ts | 66 +++++++++++++++++++ packages/db/prisma/data-migrations/index.ts | 1 + 3 files changed, 68 insertions(+) create mode 100644 packages/db/prisma/data-migrations/2023-08-07_permissions.ts diff --git a/packages/db/generated/permission.ts b/packages/db/generated/permission.ts index 4df6bb873a..6cf6640427 100644 --- a/packages/db/generated/permission.ts +++ b/packages/db/generated/permission.ts @@ -39,6 +39,7 @@ export const permissions = [ 'publishService', 'root', 'showUserReview', + 'undeleteUserReview', 'unpublishLocation', 'unpublishOrg', 'unpublishService', diff --git a/packages/db/prisma/data-migrations/2023-08-07_permissions.ts b/packages/db/prisma/data-migrations/2023-08-07_permissions.ts new file mode 100644 index 0000000000..c295822a35 --- /dev/null +++ b/packages/db/prisma/data-migrations/2023-08-07_permissions.ts @@ -0,0 +1,66 @@ +import { type Prisma, prisma } from '~db/client' +import { formatMessage } from '~db/prisma/common' +import { type MigrationJob } from '~db/prisma/dataMigrationRunner' +import { createLogger, type JobDef, jobPostRunner } from '~db/prisma/jobPreRun' + +/** Define the job metadata here. */ +const jobDef: JobDef = { + jobId: '2023-08-07_permissions', + title: 'Add Permissions', + createdBy: 'Joe Karow', + /** Optional: Longer description for the job */ + description: undefined, +} +/** + * Job export - this variable MUST be UNIQUE + * + * Use the format `jobYYYYMMDD` and append a letter afterwards if there is already a job with this name. + * + * @example `job20230404` + * + * @example `job20230404b` + */ +export const job20230807a = { + title: `[${jobDef.jobId}] ${jobDef.title}`, + task: async (_ctx, task) => { + /** Create logging instance */ + createLogger(task, jobDef.jobId) + const log = (...args: Parameters) => (task.output = formatMessage(...args)) + /** + * Start defining your data migration from here. + * + * To log output, use `task.output = 'Message to log'` + * + * This will be written to `stdout` and to a log file in `/prisma/migration-logs/` + */ + + // Do stuff + + const permissionsToAdd: Prisma.PermissionCreateManyInput[] = [ + { + id: 'perm_01H796DFS4S46FZJSHTSR5H92Q', + name: 'viewUserReviews', + description: 'Can view reviews left by another user.', + }, + { + id: 'perm_01H79745ED9DXDY7W5EYYMXYA7', + name: 'undeleteUserReview', + description: 'Un-delete a user review', + }, + ] + + const permissionCreate = await prisma.permission.createMany({ + data: permissionsToAdd, + skipDuplicates: true, + }) + log(`Permissions created: ${permissionCreate.count}`) + + /** + * DO NOT REMOVE BELOW + * + * This writes a record to the DB to register that this migration has run successfully. + */ + await jobPostRunner(jobDef) + }, + def: jobDef, +} satisfies MigrationJob diff --git a/packages/db/prisma/data-migrations/index.ts b/packages/db/prisma/data-migrations/index.ts index fd0b78266d..1c81c9899d 100644 --- a/packages/db/prisma/data-migrations/index.ts +++ b/packages/db/prisma/data-migrations/index.ts @@ -42,4 +42,5 @@ export * from './2023-07-24_add-crisis-resources/index' export * from './2023-07-26_intl-crisis-sorting' export * from './2023-07-31_crisis-support-tags' export * from './2023-07-31_national-crisis-resources/index' +export * from './2023-08-07_permissions' // codegen:end From a9e74134dca6bcb85b75c6f915b9008013421e1d Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Tue, 8 Aug 2023 11:21:11 -0400 Subject: [PATCH 35/63] userList router caching/lazy load --- packages/api/router/savedLists/index.ts | 348 ++++-------------- .../savedLists/mutation.create.handler.ts | 27 ++ .../savedLists/mutation.create.schema.ts | 30 ++ .../mutation.createAndSaveItem.handler.ts | 35 ++ .../mutation.createAndSaveItem.schema.ts | 58 +++ .../savedLists/mutation.delete.handler.ts | 35 ++ .../savedLists/mutation.delete.schema.ts | 6 + .../savedLists/mutation.deleteItem.handler.ts | 31 ++ .../savedLists/mutation.deleteItem.schema.ts | 68 ++++ .../savedLists/mutation.saveItem.handler.ts | 31 ++ .../savedLists/mutation.saveItem.schema.ts | 46 +++ .../savedLists/mutation.shareUrl.handler.ts | 43 +++ .../savedLists/mutation.shareUrl.schema.ts | 6 + .../savedLists/mutation.unShareUrl.handler.ts | 40 ++ .../savedLists/mutation.unShareUrl.schema.ts | 6 + .../router/savedLists/query.getAll.handler.ts | 27 ++ .../savedLists/query.getById.handler.ts | 35 ++ .../router/savedLists/query.getById.schema.ts | 6 + .../savedLists/query.getByUrl.handler.ts | 23 ++ .../savedLists/query.getByUrl.schema.ts | 6 + .../savedLists/query.isSaved.handler.ts | 33 ++ .../router/savedLists/query.isSaved.schema.ts | 6 + packages/api/router/savedLists/schemas.ts | 12 + 23 files changed, 690 insertions(+), 268 deletions(-) create mode 100644 packages/api/router/savedLists/mutation.create.handler.ts create mode 100644 packages/api/router/savedLists/mutation.create.schema.ts create mode 100644 packages/api/router/savedLists/mutation.createAndSaveItem.handler.ts create mode 100644 packages/api/router/savedLists/mutation.createAndSaveItem.schema.ts create mode 100644 packages/api/router/savedLists/mutation.delete.handler.ts create mode 100644 packages/api/router/savedLists/mutation.delete.schema.ts create mode 100644 packages/api/router/savedLists/mutation.deleteItem.handler.ts create mode 100644 packages/api/router/savedLists/mutation.deleteItem.schema.ts create mode 100644 packages/api/router/savedLists/mutation.saveItem.handler.ts create mode 100644 packages/api/router/savedLists/mutation.saveItem.schema.ts create mode 100644 packages/api/router/savedLists/mutation.shareUrl.handler.ts create mode 100644 packages/api/router/savedLists/mutation.shareUrl.schema.ts create mode 100644 packages/api/router/savedLists/mutation.unShareUrl.handler.ts create mode 100644 packages/api/router/savedLists/mutation.unShareUrl.schema.ts create mode 100644 packages/api/router/savedLists/query.getAll.handler.ts create mode 100644 packages/api/router/savedLists/query.getById.handler.ts create mode 100644 packages/api/router/savedLists/query.getById.schema.ts create mode 100644 packages/api/router/savedLists/query.getByUrl.handler.ts create mode 100644 packages/api/router/savedLists/query.getByUrl.schema.ts create mode 100644 packages/api/router/savedLists/query.isSaved.handler.ts create mode 100644 packages/api/router/savedLists/query.isSaved.schema.ts create mode 100644 packages/api/router/savedLists/schemas.ts diff --git a/packages/api/router/savedLists/index.ts b/packages/api/router/savedLists/index.ts index 10aae0e8d2..1ed552e6d1 100644 --- a/packages/api/router/savedLists/index.ts +++ b/packages/api/router/savedLists/index.ts @@ -1,302 +1,114 @@ -import { TRPCError } from '@trpc/server' -import { z } from 'zod' - -import { handleError } from '~api/lib/errorHandler' -import { nanoUrl } from '~api/lib/nanoIdUrl' import { defineRouter, protectedProcedure, publicProcedure } from '~api/lib/trpc' -import { id } from '~api/schemas/common' -import { CreateAuditLog } from '~api/schemas/create/auditLog' -import { CreateListAndEntry, CreateSavedList } from '~api/schemas/create/userSavedList' -import { schemas } from '~api/schemas/savedLists' -import { DeleteSavedItem, SaveItem } from '~api/schemas/update/userSavedList' + +import * as schema from './schemas' + +const HandlerCache: Partial = {} + +type UserListHandlerCache = { + getAll: typeof import('./query.getAll.handler').getAll + getById: typeof import('./query.getById.handler').getById + getByUrl: typeof import('./query.getByUrl.handler').getByUrl + isSaved: typeof import('./query.isSaved.handler').isSaved + create: typeof import('./mutation.create.handler').create + createAndSaveItem: typeof import('./mutation.createAndSaveItem.handler').createAndSaveItem + delete: typeof import('./mutation.delete.handler').deleteList + saveItem: typeof import('./mutation.saveItem.handler').saveItem + deleteItem: typeof import('./mutation.deleteItem.handler').deleteItem + shareUrl: typeof import('./mutation.shareUrl.handler').shareUrl + unShareUrl: typeof import('./mutation.unShareUrl.handler').unShareUrl +} export const savedListRouter = defineRouter({ /** Get all saved lists for logged in user */ getAll: protectedProcedure.query(async ({ ctx }) => { - try { - const lists = await ctx.prisma.userSavedList.findMany({ - where: { - ownedById: ctx.session.user.id, - }, - select: { - _count: { - select: { - organizations: true, - services: true, - sharedWith: true, - }, - }, - id: true, - name: true, - }, - }) - return lists - } catch (error) { - handleError(error) - } + if (!HandlerCache.getAll) + HandlerCache.getAll = await import('./query.getAll.handler').then((mod) => mod.getAll) + if (!HandlerCache.getAll) throw new Error('Failed to load handler') + return HandlerCache.getAll({ ctx }) }), /** Get list by ID. List must be owned by or shared with logged in user */ - getById: protectedProcedure.input(schemas.listId).query(async ({ ctx, input }) => { - try { - const list = await ctx.prisma.userSavedList.findFirst({ - where: { - id: input.id, - OR: [ - { - ownedById: ctx.session.user.id, - }, - { - sharedWith: { - some: { - userId: ctx.session.user.id, - }, - }, - }, - ], - }, - include: { - organizations: true, - services: true, - sharedWith: true, - }, - }) - return list - } catch (error) { - handleError(error) - } + getById: protectedProcedure.input(schema.ZGetByIdSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getById) + HandlerCache.getById = await import('./query.getById.handler').then((mod) => mod.getById) + if (!HandlerCache.getById) throw new Error('Failed to load handler') + return HandlerCache.getById({ ctx, input }) }), /** Get list by shared URL slug */ - getByUrl: publicProcedure.input(schemas.urlSlug).query(async ({ ctx, input }) => { - try { - const list = await ctx.prisma.userSavedList.findUnique({ - where: { - sharedLinkKey: input.slug, - }, - include: { - organizations: true, - services: true, - }, - }) - if (!list) { - throw new TRPCError({ code: 'NOT_FOUND' }) - } - return list - } catch (error) { - handleError(error) - } + getByUrl: publicProcedure.input(schema.ZGetByUrlSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getByUrl) + HandlerCache.getByUrl = await import('./query.getByUrl.handler').then((mod) => mod.getByUrl) + if (!HandlerCache.getByUrl) throw new Error('Failed to load handler') + return HandlerCache.getByUrl({ ctx, input }) }), /** Create list for logged in user */ - create: protectedProcedure.input(CreateSavedList().inputSchema).mutation(async ({ ctx, input }) => { - try { - const { dataParser } = CreateSavedList() - - const inputData = { - actorId: ctx.session.user.id, - ownedById: ctx.session.user.id, - data: input, - operation: 'CREATE', - } satisfies z.input - - const data = dataParser.parse(inputData) - - const list = await ctx.prisma.userSavedList.create(data) - return list - } catch (error) { - handleError(error) - } + create: protectedProcedure.input(schema.ZCreateSchema().inputSchema).mutation(async ({ ctx, input }) => { + if (!HandlerCache.create) + HandlerCache.create = await import('./mutation.create.handler').then((mod) => mod.create) + if (!HandlerCache.create) throw new Error('Failed to load handler') + return HandlerCache.create({ ctx, input }) }), /** Create a new list and save an organization or service to it */ createAndSaveItem: protectedProcedure - .input(CreateListAndEntry().inputSchema) + .input(schema.ZCreateAndSaveItemSchema().inputSchema) .mutation(async ({ ctx, input }) => { - try { - const { dataParser } = CreateListAndEntry() - - const inputData = { - actorId: ctx.session.user.id, - operation: 'CREATE', - ownedById: ctx.session.user.id, - data: input, - } satisfies z.input - - const data = dataParser.parse(inputData) - const result = await ctx.prisma.userSavedList.create(data) - - const flattenedResult = { - ...result, - organizations: result.organizations.map((x) => x.organizationId), - services: result.services.map((x) => x.serviceId), - } - return flattenedResult - } catch (error) { - handleError(error) - } + if (!HandlerCache.createAndSaveItem) + HandlerCache.createAndSaveItem = await import('./mutation.createAndSaveItem.handler').then( + (mod) => mod.createAndSaveItem + ) + if (!HandlerCache.createAndSaveItem) throw new Error('Failed to load handler') + return HandlerCache.createAndSaveItem({ ctx, input }) }), /** Delete list by id for current logged in user */ - delete: protectedProcedure.input(id).mutation(async ({ ctx, input }) => { - try { - const list = await ctx.prisma.userSavedList.findFirst({ - where: { - id: input.id, - ownedById: ctx.session.user.id, - }, - }) - - if (!list) { - throw new TRPCError({ code: 'UNAUTHORIZED', message: 'List does not belong to user' }) - } - - const result = await ctx.prisma.userSavedList.delete({ - where: { - id: input.id, - }, - }) - return result - } catch (error) { - handleError(error) - } - }), - - saveItem: protectedProcedure.input(SaveItem().inputSchema).mutation(async ({ ctx, input }) => { - try { - const { dataParser } = SaveItem() - - const inputData = { - actorId: ctx.session.user.id, - ownedById: ctx.session.user.id, - operation: 'LINK', - data: input, - } satisfies z.input - const data = dataParser.parse(inputData) - - const result = await ctx.prisma.userSavedList.update(data) - const flattenedResult = { - ...result, - organizations: result.organizations.map((x) => x.organizationId), - services: result.services.map((x) => x.serviceId), - } - return flattenedResult - } catch (error) { - handleError(error) - } + delete: protectedProcedure.input(schema.ZDeleteSchema).mutation(async ({ ctx, input }) => { + if (!HandlerCache.delete) + HandlerCache.delete = await import('./mutation.delete.handler').then((mod) => mod.deleteList) + if (!HandlerCache.delete) throw new Error('Failed to load handler') + return HandlerCache.delete({ ctx, input }) }), - deleteItem: protectedProcedure.input(DeleteSavedItem().inputSchema).mutation(async ({ ctx, input }) => { - const { dataParser } = DeleteSavedItem() - - const inputData = { - actorId: ctx.session.user.id, - ownedById: ctx.session.user.id, - operation: 'UNLINK', - data: input, - } satisfies z.input - const data = dataParser.parse(inputData) - const result = await ctx.prisma.userSavedList.update(data) - const flattenedResult = { - ...result, - organizations: result.organizations.map((x) => x.organizationId), - services: result.services.map((x) => x.serviceId), - } - return flattenedResult - }), + saveItem: protectedProcedure + .input(schema.ZSaveItemSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.saveItem) + HandlerCache.saveItem = await import('./mutation.saveItem.handler').then((mod) => mod.saveItem) + if (!HandlerCache.saveItem) throw new Error('Failed to load handler') + return HandlerCache.saveItem({ ctx, input }) + }), + deleteItem: protectedProcedure + .input(schema.ZDeleteItemSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.deleteItem) + HandlerCache.deleteItem = await import('./mutation.deleteItem.handler').then((mod) => mod.deleteItem) + if (!HandlerCache.deleteItem) throw new Error('Failed to load handler') + return HandlerCache.deleteItem({ ctx, input }) + }), /** * Create url to share list * * List will be viewable by anyone who has this link. */ - shareUrl: protectedProcedure.input(schemas.listId).mutation(async ({ ctx, input }) => { - try { - const generateUniqueSlug = async (): Promise => { - const slug = nanoUrl() - const response = await ctx.prisma.userSavedList.findUnique({ - where: { - sharedLinkKey: slug, - }, - }) - if (response) { - return generateUniqueSlug() - } - return slug - } - const urlSlug = await generateUniqueSlug() - const from = { sharedLinkKey: null } - - const data = { sharedLinkKey: urlSlug } - const result = await ctx.prisma.userSavedList.update({ - where: input, - data: { - ...data, - auditLogs: CreateAuditLog({ actorId: ctx.session.user.id, operation: 'UPDATE', from, to: data }), - }, - select: { - id: true, - name: true, - sharedLinkKey: true, - }, - }) - return result - } catch (error) { - handleError(error) - } + shareUrl: protectedProcedure.input(schema.ZShareUrlSchema).mutation(async ({ ctx, input }) => { + if (!HandlerCache.shareUrl) + HandlerCache.shareUrl = await import('./mutation.shareUrl.handler').then((mod) => mod.shareUrl) + if (!HandlerCache.shareUrl) throw new Error('Failed to load handler') + return HandlerCache.shareUrl({ ctx, input }) }), /** * Delete shared URL * * Anyone who visits the old URL will be presented a 404 */ - unShareUrl: protectedProcedure.input(schemas.listId).mutation(async ({ ctx, input }) => { - try { - const result = await ctx.prisma.$transaction(async (tx) => { - const from = await tx.userSavedList.findUniqueOrThrow({ - where: input, - select: { sharedLinkKey: true }, - }) - - if (from.sharedLinkKey === null) - throw new TRPCError({ code: 'BAD_REQUEST', message: `No shared URL for listId ${input.id}` }) - - const data = { sharedLinkKey: null } - const sharedUrl = await tx.userSavedList.update({ - where: input, - data: { - ...data, - auditLogs: CreateAuditLog({ actorId: ctx.session.user.id, operation: 'UPDATE', from, to: data }), - }, - select: { - id: true, - name: true, - sharedLinkKey: true, - }, - }) - return sharedUrl - }) - return result - } catch (error) { - handleError(error) - } + unShareUrl: protectedProcedure.input(schema.ZUnShareUrlSchema).mutation(async ({ ctx, input }) => { + if (!HandlerCache.unShareUrl) + HandlerCache.unShareUrl = await import('./mutation.unShareUrl.handler').then((mod) => mod.unShareUrl) + if (!HandlerCache.unShareUrl) throw new Error('Failed to load handler') + return HandlerCache.unShareUrl({ ctx, input }) }), - isSaved: publicProcedure.input(z.string()).query(async ({ ctx, input }) => { - if (!ctx.session?.user.id) return false - - const result = await ctx.prisma.userSavedList.findMany({ - where: { - AND: [ - { ownedById: ctx.session.user.id }, - { - OR: [ - { organizations: { some: { organizationId: input } } }, - { services: { some: { serviceId: input } } }, - ], - }, - ], - }, - select: { - id: true, - name: true, - }, - }) - if (!result.length) return false - return result + isSaved: publicProcedure.input(schema.ZIsSavedSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.isSaved) + HandlerCache.isSaved = await import('./query.isSaved.handler').then((mod) => mod.isSaved) + if (!HandlerCache.isSaved) throw new Error('Failed to load handler') + return HandlerCache.isSaved({ ctx, input }) }), }) diff --git a/packages/api/router/savedLists/mutation.create.handler.ts b/packages/api/router/savedLists/mutation.create.handler.ts new file mode 100644 index 0000000000..c40e056e3f --- /dev/null +++ b/packages/api/router/savedLists/mutation.create.handler.ts @@ -0,0 +1,27 @@ +import { type z } from 'zod' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateSchema, ZCreateSchema } from './mutation.create.schema' + +export const create = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { dataParser } = ZCreateSchema() + + const inputData = { + actorId: ctx.session.user.id, + ownedById: ctx.session.user.id, + data: input, + operation: 'CREATE', + } satisfies z.input + + const data = dataParser.parse(inputData) + + const list = await prisma.userSavedList.create(data) + return list + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/savedLists/mutation.create.schema.ts b/packages/api/router/savedLists/mutation.create.schema.ts new file mode 100644 index 0000000000..699e64d0e3 --- /dev/null +++ b/packages/api/router/savedLists/mutation.create.schema.ts @@ -0,0 +1,30 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { CreateBase } from '~api/schemaBase/create' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZCreateSchema = () => { + const { dataParser: parser, inputSchema } = CreateBase(z.object({ name: z.string() })) + + const dataParser = parser + .extend({ ownedById: prefixedId('user') }) + .transform(({ actorId, operation, data, ownedById }) => { + const { name } = data + return Prisma.validator()({ + data: { + name, + ownedById, + auditLogs: CreateAuditLog({ actorId, operation, to: { name, ownedById } }), + }, + select: { + id: true, + name: true, + }, + }) + }) + + return { dataParser, inputSchema } +} +export type TCreateSchema = z.infer['inputSchema']> diff --git a/packages/api/router/savedLists/mutation.createAndSaveItem.handler.ts b/packages/api/router/savedLists/mutation.createAndSaveItem.handler.ts new file mode 100644 index 0000000000..47d1213945 --- /dev/null +++ b/packages/api/router/savedLists/mutation.createAndSaveItem.handler.ts @@ -0,0 +1,35 @@ +import { type z } from 'zod' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateAndSaveItemSchema, ZCreateAndSaveItemSchema } from './mutation.createAndSaveItem.schema' + +export const createAndSaveItem = async ({ + ctx, + input, +}: TRPCHandlerParams) => { + try { + const { dataParser } = ZCreateAndSaveItemSchema() + + const inputData = { + actorId: ctx.session.user.id, + operation: 'CREATE', + ownedById: ctx.session.user.id, + data: input, + } satisfies z.input + + const data = dataParser.parse(inputData) + const result = await prisma.userSavedList.create(data) + + const flattenedResult = { + ...result, + organizations: result.organizations.map((x) => x.organizationId), + services: result.services.map((x) => x.serviceId), + } + return flattenedResult + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/savedLists/mutation.createAndSaveItem.schema.ts b/packages/api/router/savedLists/mutation.createAndSaveItem.schema.ts new file mode 100644 index 0000000000..7ee11e2c4c --- /dev/null +++ b/packages/api/router/savedLists/mutation.createAndSaveItem.schema.ts @@ -0,0 +1,58 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { CreateBase } from '~api/schemaBase/create' +import { GenerateAuditLog } from '~api/schemas/create/auditLog' +import { prefixedId } from '~api/schemas/idPrefix' +import { createManyRequired, createOneSeparateLog } from '~api/schemas/nestedOps' + +export const ZCreateAndSaveItemSchema = () => { + const { dataParser: parser, inputSchema: input } = CreateBase( + z.object({ + name: z.string(), + organizationId: prefixedId('organization').optional(), + serviceId: prefixedId('orgService').optional(), + }) + ) + const inputSchema = input.refine( + (keys) => { + if (keys.organizationId || keys.serviceId) return true + else return false + }, + { message: 'Requires one of the following: organizationId, serviceId' } + ) + + const dataParser = parser + .extend({ ownedById: prefixedId('user') }) + .transform(({ actorId, operation, data, ownedById }) => { + const { name, organizationId, serviceId } = data + + const [organizations, orgLog] = createOneSeparateLog( + organizationId ? { organizationId } : undefined, + actorId + ) + const [services, servLog] = createOneSeparateLog(serviceId ? { serviceId } : undefined, actorId) + + return Prisma.validator()({ + data: { + name, + ownedById, + organizations, + services, + auditLogs: createManyRequired([ + GenerateAuditLog({ actorId, operation, to: { name, ownedById } }), + orgLog, + servLog, + ]), + }, + select: { + services: { select: { serviceId: true } }, + organizations: { select: { organizationId: true } }, + id: true, + }, + }) + }) + + return { dataParser, inputSchema } +} +export type TCreateAndSaveItemSchema = z.infer['inputSchema']> diff --git a/packages/api/router/savedLists/mutation.delete.handler.ts b/packages/api/router/savedLists/mutation.delete.handler.ts new file mode 100644 index 0000000000..ec54e6dc5c --- /dev/null +++ b/packages/api/router/savedLists/mutation.delete.handler.ts @@ -0,0 +1,35 @@ +import { TRPCError } from '@trpc/server' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TDeleteSchema } from './mutation.delete.schema' + +export const deleteList = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const list = await prisma.userSavedList.findUniqueOrThrow({ + where: { + id: input.id, + }, + select: { id: true, ownedById: true }, + }) + + if (list.ownedById !== ctx.session.user.id) { + throw new TRPCError({ code: 'UNAUTHORIZED', message: 'List does not belong to user' }) + } + + const result = await prisma.userSavedList.delete({ + where: { + id: input.id, + }, + select: { + id: true, + name: true, + }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/savedLists/mutation.delete.schema.ts b/packages/api/router/savedLists/mutation.delete.schema.ts new file mode 100644 index 0000000000..776a1336c8 --- /dev/null +++ b/packages/api/router/savedLists/mutation.delete.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZDeleteSchema = z.object({ id: prefixedId('userSavedList') }) +export type TDeleteSchema = z.infer diff --git a/packages/api/router/savedLists/mutation.deleteItem.handler.ts b/packages/api/router/savedLists/mutation.deleteItem.handler.ts new file mode 100644 index 0000000000..f78c4dd50d --- /dev/null +++ b/packages/api/router/savedLists/mutation.deleteItem.handler.ts @@ -0,0 +1,31 @@ +import { type z } from 'zod' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TDeleteItemSchema, ZDeleteItemSchema } from './mutation.deleteItem.schema' + +export const deleteItem = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { dataParser } = ZDeleteItemSchema() + + const inputData = { + actorId: ctx.session.user.id, + ownedById: ctx.session.user.id, + operation: 'UNLINK', + data: input, + } satisfies z.input + const data = dataParser.parse(inputData) + + const result = await prisma.userSavedList.update(data) + const flattenedResult = { + ...result, + organizations: result.organizations.map((x) => x.organizationId), + services: result.services.map((x) => x.serviceId), + } + return flattenedResult + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/savedLists/mutation.deleteItem.schema.ts b/packages/api/router/savedLists/mutation.deleteItem.schema.ts new file mode 100644 index 0000000000..77e154d3db --- /dev/null +++ b/packages/api/router/savedLists/mutation.deleteItem.schema.ts @@ -0,0 +1,68 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { CreateBase } from '~api/schemaBase/create' +import { prefixedId } from '~api/schemas/idPrefix' +import { createManyRequired, deleteOneSeparateLog } from '~api/schemas/nestedOps' + +export const ZDeleteItemSchema = () => { + const { dataParser: parser, inputSchema } = CreateBase( + z.object({ + id: prefixedId('userSavedList'), + organizationId: prefixedId('organization').optional(), + serviceId: prefixedId('orgService').optional(), + }) + ) + + const dataParser = parser + .extend({ ownedById: prefixedId('user') }) + .transform(({ actorId, ownedById, data }) => { + const { id, organizationId, serviceId } = data + const [organizations, orgLog] = deleteOneSeparateLog( + organizationId + ? { + listId_organizationId: { + listId: id, + organizationId, + }, + } + : undefined, + actorId + ) + const [services, servLog] = deleteOneSeparateLog( + serviceId + ? { + listId_serviceId: { + listId: id, + serviceId, + }, + } + : undefined, + actorId + ) + + return Prisma.validator()({ + where: { + id, + ownedById, + }, + data: { + organizations, + services, + auditLogs: createManyRequired([orgLog, servLog]), + }, + select: { + id: true, + name: true, + organizations: { + select: { organizationId: true }, + }, + services: { + select: { serviceId: true }, + }, + }, + }) + }) + return { dataParser, inputSchema } +} +export type TDeleteItemSchema = z.infer['inputSchema']> diff --git a/packages/api/router/savedLists/mutation.saveItem.handler.ts b/packages/api/router/savedLists/mutation.saveItem.handler.ts new file mode 100644 index 0000000000..bb70efe5af --- /dev/null +++ b/packages/api/router/savedLists/mutation.saveItem.handler.ts @@ -0,0 +1,31 @@ +import { type z } from 'zod' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TSaveItemSchema, ZSaveItemSchema } from './mutation.saveItem.schema' + +export const saveItem = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { dataParser } = ZSaveItemSchema() + + const inputData = { + actorId: ctx.session.user.id, + ownedById: ctx.session.user.id, + operation: 'LINK', + data: input, + } satisfies z.input + const data = dataParser.parse(inputData) + + const result = await prisma.userSavedList.update(data) + const flattenedResult = { + ...result, + organizations: result.organizations.map((x) => x.organizationId), + services: result.services.map((x) => x.serviceId), + } + return flattenedResult + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/savedLists/mutation.saveItem.schema.ts b/packages/api/router/savedLists/mutation.saveItem.schema.ts new file mode 100644 index 0000000000..5f6fbd786a --- /dev/null +++ b/packages/api/router/savedLists/mutation.saveItem.schema.ts @@ -0,0 +1,46 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { CreateBase } from '~api/schemaBase/create' +import { prefixedId } from '~api/schemas/idPrefix' +import { createManyRequired, createOneSeparateLog } from '~api/schemas/nestedOps' + +export const ZSaveItemSchema = () => { + const { dataParser: parser, inputSchema } = CreateBase( + z.object({ + id: prefixedId('userSavedList'), + organizationId: prefixedId('organization').optional(), + serviceId: prefixedId('orgService').optional(), + }) + ) + + const dataParser = parser + .extend({ ownedById: prefixedId('user') }) + .transform(({ actorId, ownedById, data }) => { + const { id, organizationId, serviceId } = data + const [organizations, orgLog] = createOneSeparateLog( + organizationId ? { organizationId } : undefined, + actorId + ) + const [services, servLog] = createOneSeparateLog(serviceId ? { serviceId } : undefined, actorId) + + return Prisma.validator()({ + where: { + id, + ownedById, + }, + data: { + organizations, + services, + auditLogs: createManyRequired([orgLog, servLog]), + }, + select: { + services: { select: { serviceId: true } }, + organizations: { select: { organizationId: true } }, + id: true, + }, + }) + }) + return { dataParser, inputSchema } +} +export type TSaveItemSchema = z.infer['inputSchema']> diff --git a/packages/api/router/savedLists/mutation.shareUrl.handler.ts b/packages/api/router/savedLists/mutation.shareUrl.handler.ts new file mode 100644 index 0000000000..703cf25f63 --- /dev/null +++ b/packages/api/router/savedLists/mutation.shareUrl.handler.ts @@ -0,0 +1,43 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { nanoUrl } from '~api/lib/nanoIdUrl' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TShareUrlSchema } from './mutation.shareUrl.schema' + +const generateUniqueSlug = async (): Promise => { + const slug = nanoUrl() + const response = await prisma.userSavedList.findUnique({ + where: { + sharedLinkKey: slug, + }, + }) + if (response) { + return generateUniqueSlug() + } + return slug +} +export const shareUrl = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const urlSlug = await generateUniqueSlug() + const from = { sharedLinkKey: null } + + const data = { sharedLinkKey: urlSlug } + const result = await prisma.userSavedList.update({ + where: input, + data: { + ...data, + auditLogs: CreateAuditLog({ actorId: ctx.session.user.id, operation: 'UPDATE', from, to: data }), + }, + select: { + id: true, + name: true, + sharedLinkKey: true, + }, + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/savedLists/mutation.shareUrl.schema.ts b/packages/api/router/savedLists/mutation.shareUrl.schema.ts new file mode 100644 index 0000000000..220f713176 --- /dev/null +++ b/packages/api/router/savedLists/mutation.shareUrl.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZShareUrlSchema = z.object({ id: prefixedId('userSavedList') }) +export type TShareUrlSchema = z.infer diff --git a/packages/api/router/savedLists/mutation.unShareUrl.handler.ts b/packages/api/router/savedLists/mutation.unShareUrl.handler.ts new file mode 100644 index 0000000000..232754de84 --- /dev/null +++ b/packages/api/router/savedLists/mutation.unShareUrl.handler.ts @@ -0,0 +1,40 @@ +import { TRPCError } from '@trpc/server' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TUnShareUrlSchema } from './mutation.unShareUrl.schema' + +export const unShareUrl = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const result = await prisma.$transaction(async (tx) => { + const from = await tx.userSavedList.findUniqueOrThrow({ + where: input, + select: { sharedLinkKey: true }, + }) + + if (from.sharedLinkKey === null) + throw new TRPCError({ code: 'BAD_REQUEST', message: `No shared URL for listId ${input.id}` }) + + const data = { sharedLinkKey: null } + const sharedUrl = await tx.userSavedList.update({ + where: input, + data: { + ...data, + auditLogs: CreateAuditLog({ actorId: ctx.session.user.id, operation: 'UPDATE', from, to: data }), + }, + select: { + id: true, + name: true, + sharedLinkKey: true, + }, + }) + return sharedUrl + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/savedLists/mutation.unShareUrl.schema.ts b/packages/api/router/savedLists/mutation.unShareUrl.schema.ts new file mode 100644 index 0000000000..27b3b7d292 --- /dev/null +++ b/packages/api/router/savedLists/mutation.unShareUrl.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZUnShareUrlSchema = z.object({ id: prefixedId('userSavedList') }) +export type TUnShareUrlSchema = z.infer diff --git a/packages/api/router/savedLists/query.getAll.handler.ts b/packages/api/router/savedLists/query.getAll.handler.ts new file mode 100644 index 0000000000..04aca33afe --- /dev/null +++ b/packages/api/router/savedLists/query.getAll.handler.ts @@ -0,0 +1,27 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +export const getAll = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const lists = await prisma.userSavedList.findMany({ + where: { + ownedById: ctx.session.user.id, + }, + select: { + _count: { + select: { + organizations: true, + services: true, + sharedWith: true, + }, + }, + id: true, + name: true, + }, + }) + return lists + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/savedLists/query.getById.handler.ts b/packages/api/router/savedLists/query.getById.handler.ts new file mode 100644 index 0000000000..1767fe5cc2 --- /dev/null +++ b/packages/api/router/savedLists/query.getById.handler.ts @@ -0,0 +1,35 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetByIdSchema } from './query.getById.schema' + +export const getById = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const list = await prisma.userSavedList.findFirst({ + where: { + id: input.id, + OR: [ + { + ownedById: ctx.session.user.id, + }, + { + sharedWith: { + some: { + userId: ctx.session.user.id, + }, + }, + }, + ], + }, + include: { + organizations: true, + services: true, + sharedWith: true, + }, + }) + return list + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/savedLists/query.getById.schema.ts b/packages/api/router/savedLists/query.getById.schema.ts new file mode 100644 index 0000000000..e061785653 --- /dev/null +++ b/packages/api/router/savedLists/query.getById.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetByIdSchema = z.object({ id: prefixedId('userSavedList') }) +export type TGetByIdSchema = z.infer diff --git a/packages/api/router/savedLists/query.getByUrl.handler.ts b/packages/api/router/savedLists/query.getByUrl.handler.ts new file mode 100644 index 0000000000..372f0936dc --- /dev/null +++ b/packages/api/router/savedLists/query.getByUrl.handler.ts @@ -0,0 +1,23 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TGetByUrlSchema } from './query.getByUrl.schema' + +export const getByUrl = async ({ input }: TRPCHandlerParams) => { + try { + const list = await prisma.userSavedList.findUniqueOrThrow({ + where: { + sharedLinkKey: input.slug, + }, + include: { + organizations: true, + services: true, + }, + }) + + return list + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/savedLists/query.getByUrl.schema.ts b/packages/api/router/savedLists/query.getByUrl.schema.ts new file mode 100644 index 0000000000..6eebda8943 --- /dev/null +++ b/packages/api/router/savedLists/query.getByUrl.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZGetByUrlSchema = z.object({ slug: z.string() }) +export type TGetByUrlSchema = z.infer diff --git a/packages/api/router/savedLists/query.isSaved.handler.ts b/packages/api/router/savedLists/query.isSaved.handler.ts new file mode 100644 index 0000000000..5f37e1cd2c --- /dev/null +++ b/packages/api/router/savedLists/query.isSaved.handler.ts @@ -0,0 +1,33 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TIsSavedSchema } from './query.isSaved.schema' + +export const isSaved = async ({ ctx, input }: TRPCHandlerParams) => { + try { + if (!ctx.session?.user.id) return false + + const result = await prisma.userSavedList.findMany({ + where: { + AND: [ + { ownedById: ctx.session.user.id }, + { + OR: [ + { organizations: { some: { organizationId: input } } }, + { services: { some: { serviceId: input } } }, + ], + }, + ], + }, + select: { + id: true, + name: true, + }, + }) + if (!result.length) return false + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/savedLists/query.isSaved.schema.ts b/packages/api/router/savedLists/query.isSaved.schema.ts new file mode 100644 index 0000000000..a29bc106ac --- /dev/null +++ b/packages/api/router/savedLists/query.isSaved.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZIsSavedSchema = z.union([prefixedId('organization'), prefixedId('orgService')]) +export type TIsSavedSchema = z.infer diff --git a/packages/api/router/savedLists/schemas.ts b/packages/api/router/savedLists/schemas.ts new file mode 100644 index 0000000000..73306b6037 --- /dev/null +++ b/packages/api/router/savedLists/schemas.ts @@ -0,0 +1,12 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.create.schema' +export * from './mutation.createAndSaveItem.schema' +export * from './mutation.delete.schema' +export * from './mutation.deleteItem.schema' +export * from './mutation.saveItem.schema' +export * from './mutation.shareUrl.schema' +export * from './mutation.unShareUrl.schema' +export * from './query.getById.schema' +export * from './query.getByUrl.schema' +export * from './query.isSaved.schema' +// codegen:end From 12bc9430a577233b9e2b080eb4464ca27de39875 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Tue, 8 Aug 2023 12:05:00 -0400 Subject: [PATCH 36/63] user router caching/lazy load --- packages/api/router/user/index.ts | 301 ++++++------------ .../user/mutation.adminCreate.handler.ts | 31 ++ .../user/mutation.adminCreate.schema.ts | 133 ++++++++ .../user/mutation.confirmAccount.handler.ts | 16 + .../user/mutation.confirmAccount.schema.ts | 11 + .../router/user/mutation.create.handler.ts | 27 ++ .../api/router/user/mutation.create.schema.ts | 88 +++++ .../user/mutation.deleteAccount.handler.ts | 31 ++ .../user/mutation.deleteAccount.schema.ts | 6 + .../user/mutation.forgotPassword.handler.ts | 14 + .../user/mutation.forgotPassword.schema.ts | 14 + .../user/mutation.resetPassword.handler.ts | 15 + .../user/mutation.resetPassword.schema.ts | 12 + .../user/mutation.submitSurvey.handler.ts | 14 + .../user/mutation.submitSurvey.schema.ts | 56 ++++ .../query.getLocationPermissions.handler.ts | 20 ++ .../user/query.getOrgPermissions.handler.ts | 20 ++ .../user/query.getPermissions.handler.ts | 19 ++ .../router/user/query.getProfile.handler.ts | 25 ++ .../user/query.surveyOptions.handler.ts | 61 ++++ packages/api/router/user/schemas.ts | 9 + 21 files changed, 719 insertions(+), 204 deletions(-) create mode 100644 packages/api/router/user/mutation.adminCreate.handler.ts create mode 100644 packages/api/router/user/mutation.adminCreate.schema.ts create mode 100644 packages/api/router/user/mutation.confirmAccount.handler.ts create mode 100644 packages/api/router/user/mutation.confirmAccount.schema.ts create mode 100644 packages/api/router/user/mutation.create.handler.ts create mode 100644 packages/api/router/user/mutation.create.schema.ts create mode 100644 packages/api/router/user/mutation.deleteAccount.handler.ts create mode 100644 packages/api/router/user/mutation.deleteAccount.schema.ts create mode 100644 packages/api/router/user/mutation.forgotPassword.handler.ts create mode 100644 packages/api/router/user/mutation.forgotPassword.schema.ts create mode 100644 packages/api/router/user/mutation.resetPassword.handler.ts create mode 100644 packages/api/router/user/mutation.resetPassword.schema.ts create mode 100644 packages/api/router/user/mutation.submitSurvey.handler.ts create mode 100644 packages/api/router/user/mutation.submitSurvey.schema.ts create mode 100644 packages/api/router/user/query.getLocationPermissions.handler.ts create mode 100644 packages/api/router/user/query.getOrgPermissions.handler.ts create mode 100644 packages/api/router/user/query.getPermissions.handler.ts create mode 100644 packages/api/router/user/query.getProfile.handler.ts create mode 100644 packages/api/router/user/query.surveyOptions.handler.ts create mode 100644 packages/api/router/user/schemas.ts diff --git a/packages/api/router/user/index.ts b/packages/api/router/user/index.ts index 9a5f35aab3..ba2b75563d 100644 --- a/packages/api/router/user/index.ts +++ b/packages/api/router/user/index.ts @@ -1,224 +1,117 @@ -import { TRPCError } from '@trpc/server' -import { z } from 'zod' - -import { - confirmAccount, - createCognitoUser, - deleteAccount, - forgotPassword, - resetPassword, - userLogin, -} from '@weareinreach/auth/lib' -import { Prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { adminProcedure, defineRouter, protectedProcedure, publicProcedure } from '~api/lib/trpc' -import { - AdminCreateUser, - type AdminCreateUserInput, - CognitoBase64, - CreateUser, - CreateUserSurvey, - ForgotPassword, - ResetPassword, -} from '~api/schemas/create/user' -export const userRouter = defineRouter({ - create: publicProcedure.input(CreateUser).mutation(async ({ ctx, input }) => { - // TODO: [IN-793] Alter signup input to match with Signup Flow data. - try { - const newUser = await ctx.prisma.$transaction(async (tx) => { - const user = await tx.user.create(input.prisma) - if (user.id !== input.cognito.databaseId) throw new Error('Database ID mismatch') - const cognitoUser = await createCognitoUser(input.cognito) - if (cognitoUser?.prismaAccount) await tx.account.create(cognitoUser.prismaAccount) +import * as schema from './schemas' - if (user.id && cognitoUser?.cognitoId) return { success: true } - }) - return newUser - } catch (error) { - if (error instanceof Prisma.PrismaClientKnownRequestError) { - if (error.code === 'P2002') throw new TRPCError({ code: 'CONFLICT', message: 'User already exists' }) - } - handleError(error) - } - }), - adminCreate: adminProcedure.input(AdminCreateUser().inputSchema).mutation(async ({ ctx, input }) => { - try { - const inputData = { - actorId: ctx.session.user.id, - operation: 'CREATE', - data: input, - } satisfies AdminCreateUserInput +const HandlerCache: Partial = {} + +type UserHandlerCache = { + create: typeof import('./mutation.create.handler').create + adminCreate: typeof import('./mutation.adminCreate.handler').adminCreate + submitSurvey: typeof import('./mutation.submitSurvey.handler').submitSurvey + getProfile: typeof import('./query.getProfile.handler').getProfile + getPermissions: typeof import('./query.getPermissions.handler').getPermissions + getOrgPermissions: typeof import('./query.getOrgPermissions.handler').getOrgPermissions + getLocationPermissions: typeof import('./query.getLocationPermissions.handler').getLocationPermissions + surveyOptions: typeof import('./query.surveyOptions.handler').surveyOptions + forgotPassword: typeof import('./mutation.forgotPassword.handler').forgotPassword + confirmAccount: typeof import('./mutation.confirmAccount.handler').confirmAccount + resetPassword: typeof import('./mutation.resetPassword.handler').resetPassword + deleteAccount: typeof import('./mutation.deleteAccount.handler').deleteAccount +} - const recordData = AdminCreateUser().dataParser.parse(inputData) - const newUser = await ctx.prisma.$transaction(async (tx) => { - const user = await tx.user.create(recordData.prisma) - const cognitoUser = await createCognitoUser(recordData.cognito) - return { - user, - cognitoUser, - } - }) - return newUser - } catch (error) { - handleError(error) - } +export const userRouter = defineRouter({ + create: publicProcedure.input(schema.ZCreateSchema).mutation(async ({ ctx, input }) => { + if (!HandlerCache.create) + HandlerCache.create = await import('./mutation.create.handler').then((mod) => mod.create) + if (!HandlerCache.create) throw new Error('Failed to load handler') + return HandlerCache.create({ ctx, input }) }), - submitSurvey: publicProcedure.input(CreateUserSurvey).mutation(async ({ ctx, input }) => { - try { - const survey = await ctx.prisma.userSurvey.create(input) - return survey.id - } catch (error) { - handleError(error) - } + adminCreate: adminProcedure + .input(schema.ZAdminCreateSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.adminCreate) + HandlerCache.adminCreate = await import('./mutation.adminCreate.handler').then( + (mod) => mod.adminCreate + ) + if (!HandlerCache.adminCreate) throw new Error('Failed to load handler') + return HandlerCache.adminCreate({ ctx, input }) + }), + submitSurvey: publicProcedure.input(schema.ZSubmitSurveySchema).mutation(async ({ ctx, input }) => { + if (!HandlerCache.submitSurvey) + HandlerCache.submitSurvey = await import('./mutation.submitSurvey.handler').then( + (mod) => mod.submitSurvey + ) + if (!HandlerCache.submitSurvey) throw new Error('Failed to load handler') + return HandlerCache.submitSurvey({ ctx, input }) }), getProfile: protectedProcedure.query(async ({ ctx }) => { - try { - const profile = await ctx.prisma.user.findFirst({ - where: { - id: ctx.session.user.id, - }, - select: { - id: true, - createdAt: true, - updatedAt: true, - name: true, - email: true, - image: true, - active: true, - }, - }) - return profile - } catch (error) { - handleError(error) - } + if (!HandlerCache.getProfile) + HandlerCache.getProfile = await import('./query.getProfile.handler').then((mod) => mod.getProfile) + if (!HandlerCache.getProfile) throw new Error('Failed to load handler') + return HandlerCache.getProfile({ ctx }) }), getPermissions: protectedProcedure.query(async ({ ctx }) => { - try { - const permissions = await ctx.prisma.userPermission.findMany({ - where: { - userId: ctx.session.user.id, - }, - include: { - permission: true, - }, - }) - return permissions - } catch (error) { - handleError(error) - } + if (!HandlerCache.getPermissions) + HandlerCache.getPermissions = await import('./query.getPermissions.handler').then( + (mod) => mod.getPermissions + ) + if (!HandlerCache.getPermissions) throw new Error('Failed to load handler') + return HandlerCache.getPermissions({ ctx }) }), getOrgPermissions: protectedProcedure.query(async ({ ctx }) => { - try { - const permissions = await ctx.prisma.organizationPermission.findMany({ - where: { - userId: ctx.session.user.id, - }, - include: { - organization: true, - permission: true, - }, - }) - return permissions - } catch (error) { - handleError(error) - } + if (!HandlerCache.getOrgPermissions) + HandlerCache.getOrgPermissions = await import('./query.getOrgPermissions.handler').then( + (mod) => mod.getOrgPermissions + ) + if (!HandlerCache.getOrgPermissions) throw new Error('Failed to load handler') + return HandlerCache.getOrgPermissions({ ctx }) }), getLocationPermissions: protectedProcedure.query(async ({ ctx }) => { - try { - const permissions = await ctx.prisma.locationPermission.findMany({ - where: { - userId: ctx.session.user.id, - }, - include: { - location: true, - permission: true, - }, - }) - return permissions - } catch (error) { - handleError(error) - } + if (!HandlerCache.getLocationPermissions) + HandlerCache.getLocationPermissions = await import('./query.getLocationPermissions.handler').then( + (mod) => mod.getLocationPermissions + ) + if (!HandlerCache.getLocationPermissions) throw new Error('Failed to load handler') + return HandlerCache.getLocationPermissions({ ctx }) }), - surveyOptions: publicProcedure.query(async ({ ctx }) => { - const commonSelect = { id: true, tsKey: true, tsNs: true } - - const immigration = await ctx.prisma.userImmigration.findMany({ - select: { - ...commonSelect, - status: true, - }, - orderBy: { - status: 'asc', - }, - }) - const sog = await ctx.prisma.userSOGIdentity.findMany({ - select: { - ...commonSelect, - identifyAs: true, - }, - orderBy: { - identifyAs: 'asc', - }, - }) - const ethnicity = await ctx.prisma.userEthnicity.findMany({ - select: { - ...commonSelect, - ethnicity: true, - }, - orderBy: { - ethnicity: 'asc', - }, - }) - const community = await ctx.prisma.userCommunity.findMany({ - select: { - ...commonSelect, - community: true, - }, - orderBy: { - community: 'asc', - }, - }) - const countries = await ctx.prisma.country.findMany({ - select: { - ...commonSelect, - cca2: true, - }, - orderBy: { - name: 'asc', - }, - }) - return { community, countries, ethnicity, immigration, sog } + surveyOptions: publicProcedure.query(async () => { + if (!HandlerCache.surveyOptions) + HandlerCache.surveyOptions = await import('./query.surveyOptions.handler').then( + (mod) => mod.surveyOptions + ) + if (!HandlerCache.surveyOptions) throw new Error('Failed to load handler') + return HandlerCache.surveyOptions() }), - forgotPassword: publicProcedure.input(ForgotPassword).mutation(async ({ input }) => { - const response = await forgotPassword(input) - return response + forgotPassword: publicProcedure.input(schema.ZForgotPasswordSchema).mutation(async ({ ctx, input }) => { + if (!HandlerCache.forgotPassword) + HandlerCache.forgotPassword = await import('./mutation.forgotPassword.handler').then( + (mod) => mod.forgotPassword + ) + if (!HandlerCache.forgotPassword) throw new Error('Failed to load handler') + return HandlerCache.forgotPassword({ ctx, input }) }), - confirmAccount: publicProcedure.input(CognitoBase64).mutation(async ({ input }) => { - const { code, email } = input - const response = await confirmAccount(email, code) - return response + confirmAccount: publicProcedure.input(schema.ZConfirmAccountSchema).mutation(async ({ ctx, input }) => { + if (!HandlerCache.confirmAccount) + HandlerCache.confirmAccount = await import('./mutation.confirmAccount.handler').then( + (mod) => mod.confirmAccount + ) + if (!HandlerCache.confirmAccount) throw new Error('Failed to load handler') + return HandlerCache.confirmAccount({ ctx, input }) }), - resetPassword: publicProcedure.input(ResetPassword).mutation(async ({ input }) => { - const { code, password, email } = input - const response = await resetPassword({ code, email, password }) - return response + resetPassword: publicProcedure.input(schema.ZResetPasswordSchema).mutation(async ({ ctx, input }) => { + if (!HandlerCache.resetPassword) + HandlerCache.resetPassword = await import('./mutation.resetPassword.handler').then( + (mod) => mod.resetPassword + ) + if (!HandlerCache.resetPassword) throw new Error('Failed to load handler') + return HandlerCache.resetPassword({ ctx, input }) }), - deleteAccount: protectedProcedure.input(z.string()).mutation(async ({ input, ctx }) => { - const { email } = ctx.session.user - const cognitoSession = await userLogin(email, input) - if (cognitoSession.success) { - const successful = await ctx.prisma.$transaction(async (tx) => { - await tx.user.update({ - where: { email }, - data: { active: false }, - }) - - await deleteAccount(email) - return true - }) - if (successful) return true - } - throw new TRPCError({ code: 'UNAUTHORIZED', message: 'incorrect credentials' }) + deleteAccount: protectedProcedure.input(schema.ZDeleteAccountSchema).mutation(async ({ input, ctx }) => { + if (!HandlerCache.deleteAccount) + HandlerCache.deleteAccount = await import('./mutation.deleteAccount.handler').then( + (mod) => mod.deleteAccount + ) + if (!HandlerCache.deleteAccount) throw new Error('Failed to load handler') + return HandlerCache.deleteAccount({ ctx, input }) }), }) diff --git a/packages/api/router/user/mutation.adminCreate.handler.ts b/packages/api/router/user/mutation.adminCreate.handler.ts new file mode 100644 index 0000000000..4650fb6a8e --- /dev/null +++ b/packages/api/router/user/mutation.adminCreate.handler.ts @@ -0,0 +1,31 @@ +import { type z } from 'zod' + +import { createCognitoUser } from '@weareinreach/auth/lib/createUser' +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TAdminCreateSchema, ZAdminCreateSchema } from './mutation.adminCreate.schema' + +export const adminCreate = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const inputData = { + actorId: ctx.session.user.id, + operation: 'CREATE', + data: input, + } satisfies z.input['dataParser']> + + const recordData = ZAdminCreateSchema().dataParser.parse(inputData) + const newUser = await prisma.$transaction(async (tx) => { + const user = await tx.user.create(recordData.prisma) + const cognitoUser = await createCognitoUser(recordData.cognito) + return { + user, + cognitoUser, + } + }) + return newUser + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/user/mutation.adminCreate.schema.ts b/packages/api/router/user/mutation.adminCreate.schema.ts new file mode 100644 index 0000000000..9e2cbd9bd5 --- /dev/null +++ b/packages/api/router/user/mutation.adminCreate.schema.ts @@ -0,0 +1,133 @@ +import { z } from 'zod' + +import { generateId, Prisma } from '@weareinreach/db' +import { userTypes } from '@weareinreach/db/generated/userType' +import { CreateBase } from '~api/schemaBase/create' +import { GenerateAuditLog } from '~api/schemas/create/auditLog' +import { prefixedId } from '~api/schemas/idPrefix' +import { connectOne, connectOneRequired, createManyRequired, linkManyWithAudit } from '~api/schemas/nestedOps' + +export const ZAdminCreateSchema = () => { + const { dataParser: parser, inputSchema } = CreateBase( + z.object({ + id: prefixedId('user').default(generateId('user')), + email: z.string().email(), + password: z.string(), + name: z.string().optional(), + image: z.string().url().optional(), + active: z.boolean().optional(), + currentCity: z.string().optional(), + /** Defaults to `en` */ + language: z.string().default('en'), + /** + * Requires either `id`, `cca2`, or `cca3` + * + * `cca2` = {@link https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 ISO 3166-1 alpha-2 Country code} + * + * `cca3` = {@link https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3 ISO 3166-1 alpha-3 Country code} + */ + currentCountry: z + .union([ + z.object({ id: prefixedId('country') }), + z.object({ cca2: z.string().length(2) }), + z.object({ cca3: z.string().length(3) }), + ]) + .optional(), + /** Requires either `id` or `slug` */ + currentGovDist: z + .union([z.object({ id: prefixedId('govDist') }), z.object({ slug: z.string() })]) + .optional(), + cognitoMessage: z.string().default('Confirm your account'), + cogintoSubject: z.string().default('Click the following link to confirm your account:'), + lawPractice: z.string().optional(), + otherLawPractice: z.string().optional(), + servProvider: z.string().optional(), + servProviderOther: z.string().optional(), + // Admin fields + userType: z.enum(userTypes), + permissions: z + .object({ permissionId: prefixedId('permission') }) + .array() + .optional(), + orgPermission: z + .object({ + permissionId: prefixedId('permission'), + organizationId: prefixedId('organization'), + authorized: z.boolean().default(false), + }) + .array() + .optional(), + locationPermission: z + .object({ + permissionId: prefixedId('permission'), + orgLocationId: prefixedId('orgLocation'), + authorized: z.boolean().default(false), + }) + .array() + .optional(), + roles: z + .object({ + roleId: prefixedId('userRole'), + }) + .array() + .optional(), + }) + ) + const dataParser = parser.transform(({ actorId, data, operation }) => { + const { id, name, email, password, image, cogintoSubject, cognitoMessage } = data + const [permissions, permissionLogs] = linkManyWithAudit(data?.permissions, actorId) + const [orgPermission, orgPermissionLogs] = linkManyWithAudit(data?.orgPermission, actorId, { + auditDataKeys: ['authorized'], + }) + const [locationPermission, locationPermissionLogs] = linkManyWithAudit( + data?.locationPermission, + actorId, + { auditDataKeys: ['authorized'] } + ) + const [roles, rolesLogs] = linkManyWithAudit(data?.roles, actorId) + const userType = connectOneRequired({ type: data.userType }) + const langPref = connectOne({ localeCode: data.language }) + + const scalarData = { + id, + name, + email, + image, + langPref, + userType, + } satisfies Prisma.UserCreateInput + + return { + prisma: Prisma.validator()({ + data: { + ...scalarData, + permissions, + roles, + orgPermission, + locationPermission, + auditLogs: createManyRequired([ + GenerateAuditLog({ + actorId, + operation, + to: scalarData, + }), + ...permissionLogs, + ...rolesLogs, + ...orgPermissionLogs, + ...locationPermissionLogs, + ]), + }, + select: { id: true }, + }), + cognito: { + databaseId: id, + email, + password, + message: cognitoMessage, + subject: cogintoSubject, + }, + } + }) + return { dataParser, inputSchema } +} +export type TAdminCreateSchema = z.infer['inputSchema']> diff --git a/packages/api/router/user/mutation.confirmAccount.handler.ts b/packages/api/router/user/mutation.confirmAccount.handler.ts new file mode 100644 index 0000000000..05aa1c3e84 --- /dev/null +++ b/packages/api/router/user/mutation.confirmAccount.handler.ts @@ -0,0 +1,16 @@ +import { confirmAccount as cognitoConfirmAccount } from '@weareinreach/auth/lib/confirmAccount' +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TConfirmAccountSchema } from './mutation.confirmAccount.schema' + +export const confirmAccount = async ({ input }: TRPCHandlerParams) => { + try { + const { code, email } = input + const response = await cognitoConfirmAccount(email, code) + return response + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/user/mutation.confirmAccount.schema.ts b/packages/api/router/user/mutation.confirmAccount.schema.ts new file mode 100644 index 0000000000..36c953763b --- /dev/null +++ b/packages/api/router/user/mutation.confirmAccount.schema.ts @@ -0,0 +1,11 @@ +import { z } from 'zod' + +import { decodeUrl } from '~api/lib/encodeUrl' + +export const ZConfirmAccountSchema = z + .object({ + data: z.string(), + code: z.string(), + }) + .transform(({ data, code }) => ({ code, ...decodeUrl(data) })) +export type TConfirmAccountSchema = z.infer diff --git a/packages/api/router/user/mutation.create.handler.ts b/packages/api/router/user/mutation.create.handler.ts new file mode 100644 index 0000000000..b8fdcc3027 --- /dev/null +++ b/packages/api/router/user/mutation.create.handler.ts @@ -0,0 +1,27 @@ +import { TRPCError } from '@trpc/server' + +import { createCognitoUser } from '@weareinreach/auth/lib/createUser' +import { Prisma, prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateSchema } from './mutation.create.schema' + +export const create = async ({ input }: TRPCHandlerParams) => { + try { + const newUser = await prisma.$transaction(async (tx) => { + const user = await tx.user.create(input.prisma) + if (user.id !== input.cognito.databaseId) throw new Error('Database ID mismatch') + const cognitoUser = await createCognitoUser(input.cognito) + if (cognitoUser?.prismaAccount) await tx.account.create(cognitoUser.prismaAccount) + + if (user.id && cognitoUser?.cognitoId) return { success: true } + }) + return newUser + } catch (error) { + if (error instanceof Prisma.PrismaClientKnownRequestError) { + if (error.code === 'P2002') throw new TRPCError({ code: 'CONFLICT', message: 'User already exists' }) + } + handleError(error) + } +} diff --git a/packages/api/router/user/mutation.create.schema.ts b/packages/api/router/user/mutation.create.schema.ts new file mode 100644 index 0000000000..c8234e4b66 --- /dev/null +++ b/packages/api/router/user/mutation.create.schema.ts @@ -0,0 +1,88 @@ +import { z } from 'zod' + +import { generateId, Prisma } from '@weareinreach/db' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { prefixedId } from '~api/schemas/idPrefix' +import { connectOne, connectOneRequired } from '~api/schemas/nestedOps' + +export const ZCreateSchema = z + .object({ + id: prefixedId('user').default(generateId('user')), + email: z.string().email(), + password: z.string(), + name: z.string().optional(), + image: z.string().url().optional(), + active: z.boolean().optional(), + currentCity: z.string().optional(), + /** Defaults to `en` */ + language: z.string().default('en'), + /** + * Requires either `id`, `cca2`, or `cca3` + * + * `cca2` = {@link https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 ISO 3166-1 alpha-2 Country code} + * + * `cca3` = {@link https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3 ISO 3166-1 alpha-3 Country code} + */ + currentCountry: z + .union([ + z.object({ id: prefixedId('country') }), + z.object({ cca2: z.string().length(2) }), + z.object({ cca3: z.string().length(3) }), + ]) + .optional(), + /** Requires either `id` or `slug` */ + currentGovDist: z + .union([z.object({ id: prefixedId('govDist') }), z.object({ slug: z.string() })]) + .optional(), + userType: z.string().default('seeker'), + cognitoMessage: z.string().default('Confirm your account'), + cogintoSubject: z.string().default('Click the following link to confirm your account:'), + lawPractice: z.string().optional(), + otherLawPractice: z.string().optional(), + servProvider: z.string().optional(), + servProviderOther: z.string().optional(), + }) + .transform(({ id, name, email, password, image, active, currentCity, ...data }) => { + const userType = connectOneRequired({ type: data.userType }) + const langPref = connectOne({ localeCode: data.language }) + const currentCountry = connectOne(data.currentCountry) + const currentGovDist = connectOne(data.currentGovDist) + const { lawPractice, otherLawPractice, servProvider, servProviderOther } = data + const record = { + id, + name, + email, + image, + userType, + langPref, + currentCity, + currentCountry, + currentGovDist, + ...(lawPractice || otherLawPractice || servProvider || servProviderOther + ? { signupData: { lawPractice, otherLawPractice, servProvider, servProviderOther } } + : {}), + } satisfies Prisma.UserCreateArgs['data'] + + return { + prisma: Prisma.validator()({ + data: { + ...record, + auditLogs: CreateAuditLog({ + actorId: id, + operation: 'CREATE', + to: record, + }), + }, + select: { id: true }, + }), + + cognito: { + databaseId: id, + email, + password, + message: data.cognitoMessage, + subject: data.cogintoSubject, + }, + } + }) +export type TCreateSchema = z.infer diff --git a/packages/api/router/user/mutation.deleteAccount.handler.ts b/packages/api/router/user/mutation.deleteAccount.handler.ts new file mode 100644 index 0000000000..c9ad0050c1 --- /dev/null +++ b/packages/api/router/user/mutation.deleteAccount.handler.ts @@ -0,0 +1,31 @@ +import { TRPCError } from '@trpc/server' + +import { deleteAccount as cognitoDeleteAccount } from '@weareinreach/auth/lib/deleteAccount' +import { userLogin } from '@weareinreach/auth/lib/userLogin' +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TDeleteAccountSchema } from './mutation.deleteAccount.schema' + +export const deleteAccount = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { email } = ctx.session.user + const cognitoSession = await userLogin(email, input) + if (cognitoSession.success) { + const successful = await prisma.$transaction(async (tx) => { + await tx.user.update({ + where: { email }, + data: { active: false }, + }) + + await cognitoDeleteAccount(email) + return true + }) + if (successful) return true + } + throw new TRPCError({ code: 'UNAUTHORIZED', message: 'incorrect credentials' }) + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/user/mutation.deleteAccount.schema.ts b/packages/api/router/user/mutation.deleteAccount.schema.ts new file mode 100644 index 0000000000..68416cac21 --- /dev/null +++ b/packages/api/router/user/mutation.deleteAccount.schema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' + +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZDeleteAccountSchema = z.string() +export type TDeleteAccountSchema = z.infer diff --git a/packages/api/router/user/mutation.forgotPassword.handler.ts b/packages/api/router/user/mutation.forgotPassword.handler.ts new file mode 100644 index 0000000000..53641dc22b --- /dev/null +++ b/packages/api/router/user/mutation.forgotPassword.handler.ts @@ -0,0 +1,14 @@ +import { forgotPassword as cognitoForgotPassword } from '@weareinreach/auth/lib/forgotPassword' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TForgotPasswordSchema } from './mutation.forgotPassword.schema' + +export const forgotPassword = async ({ input }: TRPCHandlerParams) => { + try { + const response = await cognitoForgotPassword(input) + return response + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/user/mutation.forgotPassword.schema.ts b/packages/api/router/user/mutation.forgotPassword.schema.ts new file mode 100644 index 0000000000..7088e34f02 --- /dev/null +++ b/packages/api/router/user/mutation.forgotPassword.schema.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' + +export const ZForgotPasswordSchema = z + .object({ + email: z.string().email(), + cognitoMessage: z.string(), + cognitoSubject: z.string(), + }) + .transform(({ email, cognitoMessage, cognitoSubject }) => ({ + email, + subject: cognitoSubject, + message: cognitoMessage, + })) +export type TForgotPasswordSchema = z.infer diff --git a/packages/api/router/user/mutation.resetPassword.handler.ts b/packages/api/router/user/mutation.resetPassword.handler.ts new file mode 100644 index 0000000000..c170f57dc3 --- /dev/null +++ b/packages/api/router/user/mutation.resetPassword.handler.ts @@ -0,0 +1,15 @@ +import { resetPassword as cognitoResetPassword } from '@weareinreach/auth/lib/resetPassword' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TResetPasswordSchema } from './mutation.resetPassword.schema' + +export const resetPassword = async ({ ctx, input }: TRPCHandlerParams) => { + try { + const { code, password, email } = input + const response = await cognitoResetPassword({ code, email, password }) + return response + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/user/mutation.resetPassword.schema.ts b/packages/api/router/user/mutation.resetPassword.schema.ts new file mode 100644 index 0000000000..4409fe256d --- /dev/null +++ b/packages/api/router/user/mutation.resetPassword.schema.ts @@ -0,0 +1,12 @@ +import { z } from 'zod' + +import { decodeUrl } from '~api/lib/encodeUrl' + +export const ZResetPasswordSchema = z + .object({ + data: z.string(), + code: z.string(), + password: z.string(), + }) + .transform(({ data, password, code }) => ({ password, code, ...decodeUrl(data) })) +export type TResetPasswordSchema = z.infer diff --git a/packages/api/router/user/mutation.submitSurvey.handler.ts b/packages/api/router/user/mutation.submitSurvey.handler.ts new file mode 100644 index 0000000000..75f677032a --- /dev/null +++ b/packages/api/router/user/mutation.submitSurvey.handler.ts @@ -0,0 +1,14 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TSubmitSurveySchema } from './mutation.submitSurvey.schema' + +export const submitSurvey = async ({ input }: TRPCHandlerParams) => { + try { + const survey = await prisma.userSurvey.create(input) + return survey.id + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/user/mutation.submitSurvey.schema.ts b/packages/api/router/user/mutation.submitSurvey.schema.ts new file mode 100644 index 0000000000..bbf358910e --- /dev/null +++ b/packages/api/router/user/mutation.submitSurvey.schema.ts @@ -0,0 +1,56 @@ +import { z } from 'zod' + +import { Prisma } from '@weareinreach/db' +import { prefixedId } from '~api/schemas/idPrefix' +import { connectOne, createManyOptional } from '~api/schemas/nestedOps' + +export const ZSubmitSurveySchema = z + .object({ + birthYear: z.number(), + reasonForJoin: z.string(), + communityIds: prefixedId('userCommunity').array(), + ethnicityIds: prefixedId('userEthnicity').array(), + identifyIds: prefixedId('userSOGIdentity').array(), + countryOriginId: prefixedId('country'), + immigrationId: prefixedId('userImmigration'), + currentCity: z.string(), + currentGovDistId: prefixedId('govDist'), + currentCountryId: prefixedId('country'), + immigrationOther: z.string(), + ethnicityOther: z.string(), + }) + .partial() + .transform( + ({ + communityIds, + ethnicityIds, + identifyIds, + countryOriginId: countryOriginIdInput, + currentCountryId: currentCountryIdInput, + currentGovDistId: currentGovDistIdInput, + immigrationId: immigrationIdInput, + ...rest + }) => { + const communityId = communityIds ? communityIds.map((communityId) => ({ communityId })) : undefined + const ethnicityId = ethnicityIds ? ethnicityIds.map((ethnicityId) => ({ ethnicityId })) : undefined + const sogId = identifyIds ? identifyIds.map((sogId) => ({ sogId })) : undefined + const immigrationId = immigrationIdInput ? { id: immigrationIdInput } : undefined + const countryOriginId = countryOriginIdInput ? { id: countryOriginIdInput } : undefined + const currentCountryId = currentCountryIdInput ? { id: currentCountryIdInput } : undefined + const currentGovDistId = currentGovDistIdInput ? { id: currentGovDistIdInput } : undefined + return Prisma.validator()({ + data: { + ...rest, + communities: createManyOptional(communityId), + ethnicities: createManyOptional(ethnicityId), + identifiesAs: createManyOptional(sogId), + immigration: connectOne(immigrationId), + countryOrigin: connectOne(countryOriginId), + currentCountry: connectOne(currentCountryId), + currentGovDist: connectOne(currentGovDistId), + }, + select: { id: true }, + }) + } + ) +export type TSubmitSurveySchema = z.infer diff --git a/packages/api/router/user/query.getLocationPermissions.handler.ts b/packages/api/router/user/query.getLocationPermissions.handler.ts new file mode 100644 index 0000000000..a0b9df0a93 --- /dev/null +++ b/packages/api/router/user/query.getLocationPermissions.handler.ts @@ -0,0 +1,20 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +export const getLocationPermissions = async ({ ctx }: TRPCHandlerParams) => { + try { + const permissions = await prisma.locationPermission.findMany({ + where: { + userId: ctx.session.user.id, + }, + include: { + location: true, + permission: true, + }, + }) + return permissions + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/user/query.getOrgPermissions.handler.ts b/packages/api/router/user/query.getOrgPermissions.handler.ts new file mode 100644 index 0000000000..d585dbf913 --- /dev/null +++ b/packages/api/router/user/query.getOrgPermissions.handler.ts @@ -0,0 +1,20 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +export const getOrgPermissions = async ({ ctx }: TRPCHandlerParams) => { + try { + const permissions = await prisma.organizationPermission.findMany({ + where: { + userId: ctx.session.user.id, + }, + include: { + organization: true, + permission: true, + }, + }) + return permissions + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/user/query.getPermissions.handler.ts b/packages/api/router/user/query.getPermissions.handler.ts new file mode 100644 index 0000000000..6150c45c14 --- /dev/null +++ b/packages/api/router/user/query.getPermissions.handler.ts @@ -0,0 +1,19 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +export const getPermissions = async ({ ctx }: TRPCHandlerParams) => { + try { + const permissions = await prisma.userPermission.findMany({ + where: { + userId: ctx.session.user.id, + }, + include: { + permission: true, + }, + }) + return permissions + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/user/query.getProfile.handler.ts b/packages/api/router/user/query.getProfile.handler.ts new file mode 100644 index 0000000000..0ad0556c03 --- /dev/null +++ b/packages/api/router/user/query.getProfile.handler.ts @@ -0,0 +1,25 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +export const getProfile = async ({ ctx }: TRPCHandlerParams) => { + try { + const profile = await prisma.user.findUniqueOrThrow({ + where: { + id: ctx.session.user.id, + }, + select: { + id: true, + createdAt: true, + updatedAt: true, + name: true, + email: true, + image: true, + active: true, + }, + }) + return profile + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/user/query.surveyOptions.handler.ts b/packages/api/router/user/query.surveyOptions.handler.ts new file mode 100644 index 0000000000..49a3c85f0f --- /dev/null +++ b/packages/api/router/user/query.surveyOptions.handler.ts @@ -0,0 +1,61 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +export const surveyOptions = async () => { + try { + const commonSelect = { id: true, tsKey: true, tsNs: true } + + const [immigration, sog, ethnicity, community, countries] = await Promise.all([ + prisma.userImmigration.findMany({ + select: { + ...commonSelect, + status: true, + }, + orderBy: { + status: 'asc', + }, + }), + prisma.userSOGIdentity.findMany({ + select: { + ...commonSelect, + identifyAs: true, + }, + orderBy: { + identifyAs: 'asc', + }, + }), + prisma.userEthnicity.findMany({ + select: { + ...commonSelect, + ethnicity: true, + }, + orderBy: { + ethnicity: 'asc', + }, + }), + prisma.userCommunity.findMany({ + select: { + ...commonSelect, + community: true, + }, + orderBy: { + community: 'asc', + }, + }), + prisma.country.findMany({ + select: { + ...commonSelect, + cca2: true, + }, + orderBy: { + name: 'asc', + }, + }), + ]) + + return { community, countries, ethnicity, immigration, sog } + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/user/schemas.ts b/packages/api/router/user/schemas.ts new file mode 100644 index 0000000000..9e9b23b60e --- /dev/null +++ b/packages/api/router/user/schemas.ts @@ -0,0 +1,9 @@ +// codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.adminCreate.schema' +export * from './mutation.confirmAccount.schema' +export * from './mutation.create.schema' +export * from './mutation.deleteAccount.schema' +export * from './mutation.forgotPassword.schema' +export * from './mutation.resetPassword.schema' +export * from './mutation.submitSurvey.schema' +// codegen:end From 1493ee8202a7db5cf4885cf2cebd84168ccb1898 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Tue, 8 Aug 2023 12:51:43 -0400 Subject: [PATCH 37/63] organization router mutation caching/lazy load --- packages/api/router/organization/index.ts | 206 +++++++++++++++++- .../mutation.attachAttribute.handler.ts | 34 +++ .../mutation.attachAttribute.schema.ts | 102 +++++++++ .../mutation.createNewQuick.handler.ts | 28 +++ .../mutation.createNewQuick.schema.ts | 116 ++++++++++ .../mutation.createNewSuggestion.handler.ts | 29 +++ .../mutation.createNewSuggestion.schema.ts | 58 +++++ packages/api/router/organization/mutations.ts | 73 ------- packages/api/router/organization/queries.ts | 148 ------------- packages/api/router/organization/schemas.ts | 3 + 10 files changed, 572 insertions(+), 225 deletions(-) create mode 100644 packages/api/router/organization/mutation.attachAttribute.handler.ts create mode 100644 packages/api/router/organization/mutation.attachAttribute.schema.ts create mode 100644 packages/api/router/organization/mutation.createNewQuick.handler.ts create mode 100644 packages/api/router/organization/mutation.createNewQuick.schema.ts create mode 100644 packages/api/router/organization/mutation.createNewSuggestion.handler.ts create mode 100644 packages/api/router/organization/mutation.createNewSuggestion.schema.ts delete mode 100644 packages/api/router/organization/mutations.ts delete mode 100644 packages/api/router/organization/queries.ts diff --git a/packages/api/router/organization/index.ts b/packages/api/router/organization/index.ts index 9bfdb28e5b..8b0fd04b2a 100644 --- a/packages/api/router/organization/index.ts +++ b/packages/api/router/organization/index.ts @@ -1,6 +1,204 @@ -import { mergeRouters } from '~api/lib/trpc' +import { defineRouter, permissionedProcedure, protectedProcedure, publicProcedure } from '~api/lib/trpc' -import { mutations } from './mutations' -import { queries } from './queries' +import * as schema from './schemas' -export const orgRouter = mergeRouters(queries, mutations) +const HandlerCache: Partial = {} + +type OrganizationHandlerCache = { + // + // QUERIES + // + // #region Queries + getById: typeof import('./query.getById.handler').getById + getBySlug: typeof import('./query.getBySlug.handler').getBySlug + getIdFromSlug: typeof import('./query.getIdFromSlug.handler').getIdFromSlug + searchName: typeof import('./query.searchName.handler').searchName + searchDistance: typeof import('./query.searchDistance.handler').searchDistance + getNameFromSlug: typeof import('./query.getNameFromSlug.handler').getNameFromSlug + isSaved: typeof import('./query.isSaved.handler').isSaved + suggestionOptions: typeof import('./query.suggestionOptions.handler').suggestionOptions + checkForExisting: typeof import('./query.checkForExisting.handler').checkForExisting + generateSlug: typeof import('./query.generateSlug.handler').generateSlug + forOrgPage: typeof import('./query.forOrgPage.handler').forOrgPage + forLocationPage: typeof import('./query.forLocationPage.handler').forLocationPage + slugRedirect: typeof import('./query.slugRedirect.handler').slugRedirect + getIntlCrisis: typeof import('./query.getIntlCrisis.handler').getIntlCrisis + getNatlCrisis: typeof import('./query.getNatlCrisis.handler').getNatlCrisis + // #endregion + + // + // MUTATIONS + // + // #region Mutations + createNewQuick: typeof import('./mutation.createNewQuick.handler').createNewQuick + createNewSuggestion: typeof import('./mutation.createNewSuggestion.handler').createNewSuggestion + attachAttribute: typeof import('./mutation.attachAttribute.handler').attachAttribute + // #endregion +} + +export const orgRouter = defineRouter({ + // + // QUERIES + // + // #region Queries + getById: publicProcedure.input(schema.ZGetByIdSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getById) + HandlerCache.getById = await import('./query.getById.handler').then((mod) => mod.getById) + + if (!HandlerCache.getById) throw new Error('Failed to load handler') + return HandlerCache.getById({ ctx, input }) + }), + getBySlug: publicProcedure.input(schema.ZGetBySlugSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getBySlug) + HandlerCache.getBySlug = await import('./query.getBySlug.handler').then((mod) => mod.getBySlug) + + if (!HandlerCache.getBySlug) throw new Error('Failed to load handler') + return HandlerCache.getBySlug({ ctx, input }) + }), + getIdFromSlug: publicProcedure.input(schema.ZGetIdFromSlugSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getIdFromSlug) + HandlerCache.getIdFromSlug = await import('./query.getIdFromSlug.handler').then( + (mod) => mod.getIdFromSlug + ) + + if (!HandlerCache.getIdFromSlug) throw new Error('Failed to load handler') + return HandlerCache.getIdFromSlug({ ctx, input }) + }), + searchName: publicProcedure.input(schema.ZSearchNameSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.searchName) + HandlerCache.searchName = await import('./query.searchName.handler').then((mod) => mod.searchName) + + if (!HandlerCache.searchName) throw new Error('Failed to load handler') + return HandlerCache.searchName({ ctx, input }) + }), + searchDistance: publicProcedure.input(schema.ZSearchDistanceSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.searchDistance) + HandlerCache.searchDistance = await import('./query.searchDistance.handler').then( + (mod) => mod.searchDistance + ) + + if (!HandlerCache.searchDistance) throw new Error('Failed to load handler') + return HandlerCache.searchDistance({ ctx, input }) + }), + getNameFromSlug: publicProcedure.input(schema.ZGetNameFromSlugSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getNameFromSlug) + HandlerCache.getNameFromSlug = await import('./query.getNameFromSlug.handler').then( + (mod) => mod.getNameFromSlug + ) + + if (!HandlerCache.getNameFromSlug) throw new Error('Failed to load handler') + return HandlerCache.getNameFromSlug({ ctx, input }) + }), + isSaved: publicProcedure.input(schema.ZIsSavedSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.isSaved) + HandlerCache.isSaved = await import('./query.isSaved.handler').then((mod) => mod.isSaved) + + if (!HandlerCache.isSaved) throw new Error('Failed to load handler') + return HandlerCache.isSaved({ ctx, input }) + }), + suggestionOptions: publicProcedure.query(async () => { + if (!HandlerCache.suggestionOptions) + HandlerCache.suggestionOptions = await import('./query.suggestionOptions.handler').then( + (mod) => mod.suggestionOptions + ) + + if (!HandlerCache.suggestionOptions) throw new Error('Failed to load handler') + return HandlerCache.suggestionOptions() + }), + checkForExisting: publicProcedure.input(schema.ZCheckForExistingSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.checkForExisting) + HandlerCache.checkForExisting = await import('./query.checkForExisting.handler').then( + (mod) => mod.checkForExisting + ) + + if (!HandlerCache.checkForExisting) throw new Error('Failed to load handler') + return HandlerCache.checkForExisting({ ctx, input }) + }), + generateSlug: protectedProcedure.input(schema.ZGenerateSlugSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.generateSlug) + HandlerCache.generateSlug = await import('./query.generateSlug.handler').then((mod) => mod.generateSlug) + + if (!HandlerCache.generateSlug) throw new Error('Failed to load handler') + return HandlerCache.generateSlug({ ctx, input }) + }), + forOrgPage: publicProcedure.input(schema.ZForOrgPageSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forOrgPage) + HandlerCache.forOrgPage = await import('./query.forOrgPage.handler').then((mod) => mod.forOrgPage) + + if (!HandlerCache.forOrgPage) throw new Error('Failed to load handler') + return HandlerCache.forOrgPage({ ctx, input }) + }), + forLocationPage: publicProcedure.input(schema.ZForLocationPageSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.forLocationPage) + HandlerCache.forLocationPage = await import('./query.forLocationPage.handler').then( + (mod) => mod.forLocationPage + ) + + if (!HandlerCache.forLocationPage) throw new Error('Failed to load handler') + return HandlerCache.forLocationPage({ ctx, input }) + }), + slugRedirect: publicProcedure.input(schema.ZSlugRedirectSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.slugRedirect) + HandlerCache.slugRedirect = await import('./query.slugRedirect.handler').then((mod) => mod.slugRedirect) + + if (!HandlerCache.slugRedirect) throw new Error('Failed to load handler') + return HandlerCache.slugRedirect({ ctx, input }) + }), + getIntlCrisis: publicProcedure.input(schema.ZGetIntlCrisisSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getIntlCrisis) + HandlerCache.getIntlCrisis = await import('./query.getIntlCrisis.handler').then( + (mod) => mod.getIntlCrisis + ) + + if (!HandlerCache.getIntlCrisis) throw new Error('Failed to load handler') + return HandlerCache.getIntlCrisis({ ctx, input }) + }), + getNatlCrisis: publicProcedure.input(schema.ZGetNatlCrisisSchema).query(async ({ ctx, input }) => { + if (!HandlerCache.getNatlCrisis) + HandlerCache.getNatlCrisis = await import('./query.getNatlCrisis.handler').then( + (mod) => mod.getNatlCrisis + ) + + if (!HandlerCache.getNatlCrisis) throw new Error('Failed to load handler') + return HandlerCache.getNatlCrisis({ ctx, input }) + }), + + // #endregion + + // + // MUTATIONS + // + // #region Mutations + createNewQuick: permissionedProcedure('createNewOrgQuick') + .input(schema.ZCreateNewQuickSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.createNewQuick) + HandlerCache.createNewQuick = await import('./mutation.createNewQuick.handler').then( + (mod) => mod.createNewQuick + ) + if (!HandlerCache.createNewQuick) throw new Error('Failed to load handler') + return HandlerCache.createNewQuick({ ctx, input }) + }), + createNewSuggestion: protectedProcedure + .input(schema.ZCreateNewSuggestionSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.createNewSuggestion) + HandlerCache.createNewSuggestion = await import('./mutation.createNewSuggestion.handler').then( + (mod) => mod.createNewSuggestion + ) + if (!HandlerCache.createNewSuggestion) throw new Error('Failed to load handler') + return HandlerCache.createNewSuggestion({ ctx, input }) + }), + attachAttribute: permissionedProcedure('attachOrgAttributes') + .input(schema.ZAttachAttributeSchema().inputSchema) + .mutation(async ({ ctx, input }) => { + if (!HandlerCache.attachAttribute) + HandlerCache.attachAttribute = await import('./mutation.attachAttribute.handler').then( + (mod) => mod.attachAttribute + ) + if (!HandlerCache.attachAttribute) throw new Error('Failed to load handler') + return HandlerCache.attachAttribute({ ctx, input }) + }), + + // #endregion +}) diff --git a/packages/api/router/organization/mutation.attachAttribute.handler.ts b/packages/api/router/organization/mutation.attachAttribute.handler.ts new file mode 100644 index 0000000000..2d49695874 --- /dev/null +++ b/packages/api/router/organization/mutation.attachAttribute.handler.ts @@ -0,0 +1,34 @@ +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TAttachAttributeSchema, ZAttachAttributeSchema } from './mutation.attachAttribute.schema' + +export const attachAttribute = async ({ + ctx, + input, +}: TRPCHandlerParams) => { + try { + const inputData = { actorId: ctx.session.user.id, operation: 'LINK', data: input } + const { translationKey, freeText, attributeSupplement, organizationAttribute, auditLogs } = + ZAttachAttributeSchema().dataParser.parse(inputData) + + const result = await prisma.$transaction(async (tx) => { + const tKey = translationKey ? await tx.translationKey.create(translationKey) : undefined + const fText = freeText ? await tx.freeText.create(freeText) : undefined + const aSupp = attributeSupplement ? await tx.attributeSupplement.create(attributeSupplement) : undefined + const attrLink = await tx.organizationAttribute.create(organizationAttribute) + const logs = await tx.auditLog.createMany({ data: auditLogs, skipDuplicates: true }) + return { + translationKey: tKey, + freeText: fText, + attributeSupplement: aSupp, + organizationAttribute: attrLink, + auditLog: logs, + } + }) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/organization/mutation.attachAttribute.schema.ts b/packages/api/router/organization/mutation.attachAttribute.schema.ts new file mode 100644 index 0000000000..5a02398f18 --- /dev/null +++ b/packages/api/router/organization/mutation.attachAttribute.schema.ts @@ -0,0 +1,102 @@ +import { z } from 'zod' + +import { generateFreeText, generateId, InputJsonValue, Prisma } from '@weareinreach/db' +import { CreateBase } from '~api/schemaBase/create' +import { GenerateAuditLog } from '~api/schemas/create/auditLog' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZAttachAttributeSchema = () => { + const { dataParser: parser, inputSchema } = CreateBase( + z.object({ + organizationId: prefixedId('organization'), + attributeId: prefixedId('attribute'), + supplement: z + .object({ + data: InputJsonValue.optional(), + boolean: z.boolean().optional(), + countryId: z.string().optional(), + govDistId: z.string().optional(), + languageId: z.string().optional(), + text: z.string().optional(), + }) + .optional(), + }) + ) + + const dataParser = parser.transform(({ actorId, data: parsedData }) => { + const { organizationId, attributeId, supplement: supplementInput } = parsedData + + const supplementId = supplementInput ? generateId('attributeSupplement') : undefined + + const { freeText, translationKey } = + supplementId && supplementInput?.text + ? generateFreeText({ + orgId: organizationId, + text: supplementInput.text, + type: 'attSupp', + itemId: supplementId, + }) + : { freeText: undefined, translationKey: undefined } + + const { boolean, countryId, data, govDistId, languageId } = supplementInput ?? {} + const auditLogs = new Set() + + if (freeText && translationKey) { + auditLogs.add( + GenerateAuditLog({ + actorId, + operation: 'CREATE', + freeTextId: freeText.id, + to: translationKey, + translationKey: translationKey.key, + }) + ) + } + + const supplementData = supplementInput + ? { id: supplementId, countryId, boolean, data, govDistId, languageId, textId: freeText?.id } + : undefined + if (supplementData) + auditLogs.add( + GenerateAuditLog({ + actorId, + operation: 'CREATE', + to: supplementData, + attributeSupplementId: supplementData.id, + attributeId, + }) + ) + + auditLogs.add( + GenerateAuditLog({ + actorId, + operation: 'LINK', + organizationId, + attributeId, + attributeSupplementId: supplementData?.id, + }) + ) + + return { + freeText: freeText ? Prisma.validator()({ data: freeText }) : undefined, + translationKey: translationKey + ? Prisma.validator()({ data: translationKey }) + : undefined, + attributeSupplement: supplementData + ? Prisma.validator()({ + data: supplementData, + }) + : undefined, + organizationAttribute: Prisma.validator()({ + data: { + attribute: { connect: { id: attributeId } }, + organization: { connect: { id: organizationId } }, + supplement: supplementId ? { connect: { id: supplementId } } : undefined, + }, + }), + auditLogs: Array.from(auditLogs.values()), + } + }) + return { dataParser, inputSchema } +} +export type TAttachAttributeSchema = z.infer['inputSchema']> diff --git a/packages/api/router/organization/mutation.createNewQuick.handler.ts b/packages/api/router/organization/mutation.createNewQuick.handler.ts new file mode 100644 index 0000000000..82b2c40bf1 --- /dev/null +++ b/packages/api/router/organization/mutation.createNewQuick.handler.ts @@ -0,0 +1,28 @@ +import { type z } from 'zod' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { type TCreateNewQuickSchema, ZCreateNewQuickSchema } from './mutation.createNewQuick.schema' + +export const createNewQuick = async ({ + ctx, + input, +}: TRPCHandlerParams) => { + try { + const inputData = { + actorId: ctx.session.user.id, + operation: 'CREATE', + data: input, + } satisfies z.input['dataParser']> + + const record = ZCreateNewQuickSchema().dataParser.parse(inputData) + + const result = await prisma.organization.create(record) + + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/organization/mutation.createNewQuick.schema.ts b/packages/api/router/organization/mutation.createNewQuick.schema.ts new file mode 100644 index 0000000000..3fe8c44b66 --- /dev/null +++ b/packages/api/router/organization/mutation.createNewQuick.schema.ts @@ -0,0 +1,116 @@ +import { z } from 'zod' + +import { createGeoFields, generateId, generateNestedFreeText, Prisma } from '@weareinreach/db' +import { CreateBase } from '~api/schemaBase/create' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { prefixedId } from '~api/schemas/idPrefix' +import { createManyWithAudit } from '~api/schemas/nestedOps' + +export const ZCreateNewQuickSchema = () => { + const { dataParser: parser, inputSchema } = CreateBase( + z.object({ + id: prefixedId('organization').default(generateId('organization')), + name: z.string(), + slug: z.string(), + sourceId: prefixedId('source'), + description: z.string().optional(), + locations: z + .object({ + id: prefixedId('orgLocation').optional().default(generateId('orgLocation')), + name: z.string().optional(), + street1: z.string(), + street2: z.string().optional(), + city: z.string(), + postCode: z.string().optional(), + primary: z.boolean().optional(), + govDistId: z.string(), + countryId: z.string(), + longitude: z.number().gte(-180).lte(180), + latitude: z.number().gte(-90).lte(90), + published: z.boolean().default(false), + }) + .array() + .optional(), + emails: z + .object({ + id: prefixedId('orgEmail').optional().default(generateId('orgEmail')), + firstName: z.string().optional(), + lastName: z.string().optional(), + primary: z.boolean().default(false), + email: z.string().email(), + published: z.boolean().default(false), + }) + .array() + .optional(), + phones: z + .object({ + id: prefixedId('orgPhone').optional().default(generateId('orgPhone')), + number: z.string(), + ext: z.string().nullish(), + primary: z.boolean().default(false), + published: z.boolean().default(false), + countryId: z.string(), + phoneTypeId: z.string().optional(), + locationOnly: z.boolean().default(false), + }) + .array() + .optional(), + websites: z + .object({ + id: prefixedId('orgWebsite').optional().default(generateId('orgWebsite')), + url: z.string().url(), + }) + .array() + .optional(), + socialMedia: z + .object({ + id: prefixedId('orgSocialMedia').optional().default(generateId('orgSocialMedia')), + username: z.string(), + url: z.string().url(), + published: z.boolean().default(false), + serviceId: z.string(), + }) + .array() + .optional(), + }) + ) + + const dataParser = parser.transform(({ actorId, operation, data }) => { + const { name, slug, sourceId } = data + + const locations = data.locations?.length + ? data.locations.map(({ latitude, longitude, ...record }) => ({ + ...record, + ...createGeoFields({ latitude, longitude }), + })) + : undefined + return Prisma.validator()({ + data: { + name, + slug, + source: { + connect: { id: sourceId }, + }, + description: data.description + ? generateNestedFreeText({ orgId: data.id, type: 'orgDesc', text: data.description }) + : undefined, + locations: createManyWithAudit(locations, actorId), + emails: createManyWithAudit(data.emails, actorId), + phones: createManyWithAudit(data.phones, actorId), + socialMedia: createManyWithAudit(data.socialMedia, actorId), + websites: createManyWithAudit(data.websites, actorId), + auditLogs: CreateAuditLog({ actorId, operation, to: { name, slug, sourceId } }), + }, + include: { + description: Boolean(data.description), + locations: Boolean(data.locations), + emails: Boolean(data.emails), + phones: Boolean(data.phones), + websites: Boolean(data.websites), + socialMedia: Boolean(data.socialMedia), + }, + }) + }) + return { dataParser, inputSchema } +} +export type TCreateNewQuickSchema = z.infer['inputSchema']> diff --git a/packages/api/router/organization/mutation.createNewSuggestion.handler.ts b/packages/api/router/organization/mutation.createNewSuggestion.handler.ts new file mode 100644 index 0000000000..9715c1b941 --- /dev/null +++ b/packages/api/router/organization/mutation.createNewSuggestion.handler.ts @@ -0,0 +1,29 @@ +import { type z } from 'zod' + +import { prisma } from '@weareinreach/db' +import { handleError } from '~api/lib/errorHandler' +import { type TRPCHandlerParams } from '~api/types/handler' + +import { + type TCreateNewSuggestionSchema, + ZCreateNewSuggestionSchema, +} from './mutation.createNewSuggestion.schema' + +export const createNewSuggestion = async ({ + ctx, + input, +}: TRPCHandlerParams) => { + try { + const inputData = { + actorId: ctx.session.user.id, + operation: 'CREATE', + data: input, + } satisfies z.input['dataParser']> + + const record = ZCreateNewSuggestionSchema().dataParser.parse(inputData) + const result = await prisma.suggestion.create(record) + return result + } catch (error) { + handleError(error) + } +} diff --git a/packages/api/router/organization/mutation.createNewSuggestion.schema.ts b/packages/api/router/organization/mutation.createNewSuggestion.schema.ts new file mode 100644 index 0000000000..2a0c0119f2 --- /dev/null +++ b/packages/api/router/organization/mutation.createNewSuggestion.schema.ts @@ -0,0 +1,58 @@ +import { z } from 'zod' + +import { generateId, Prisma } from '@weareinreach/db' +import { CreateBase } from '~api/schemaBase/create' +import { CreateAuditLog } from '~api/schemas/create/auditLog' +import { prefixedId } from '~api/schemas/idPrefix' + +export const ZCreateNewSuggestionSchema = () => { + const { dataParser: parser, inputSchema } = CreateBase( + z.object({ + countryId: prefixedId('country'), + orgName: z.string().trim().min(2), + orgSlug: z.string().regex(/^[A-Za-z0-9-]*$/, 'Slug must only contain letters, numbers, and hyphens'), + orgWebsite: z.string().trim().url().optional(), + orgAddress: z + .object({ + street1: z.string(), + city: z.string(), + govDist: z.string(), + postCode: z.string(), + }) + .partial() + .nullish(), + serviceCategories: prefixedId('serviceCategory').array().nullish(), + communityFocus: prefixedId('attribute').array().nullish(), + }) + ) + const dataParser = parser.transform(({ actorId, operation, data }) => { + const { countryId, orgName, orgSlug, communityFocus, orgAddress, orgWebsite, serviceCategories } = data + const organizationId = generateId('organization') + + return Prisma.validator()({ + data: { + organization: { + create: { + id: organizationId, + name: orgName, + slug: orgSlug, + source: { connect: { source: 'suggestion' } }, + }, + }, + data: { + orgWebsite, + orgAddress, + countryId, + communityFocus, + serviceCategories, + }, + auditLogs: CreateAuditLog({ actorId, operation, to: data, organizationId }), + }, + select: { + id: true, + }, + }) + }) + return { dataParser, inputSchema } +} +export type TCreateNewSuggestionSchema = z.infer['inputSchema']> diff --git a/packages/api/router/organization/mutations.ts b/packages/api/router/organization/mutations.ts deleted file mode 100644 index 2e4cb32eae..0000000000 --- a/packages/api/router/organization/mutations.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { handleError } from '~api/lib/errorHandler' -import { defineRouter, permissionedProcedure, protectedProcedure } from '~api/lib/trpc' -import { - AttachOrgAttribute, - type CreateOrgSuggestionInput, - CreateOrgSuggestionSchema, - type CreateQuickOrgInput, - CreateQuickOrgSchema, -} from '~api/schemas/create/organization' - -export const mutations = defineRouter({ - createNewQuick: permissionedProcedure('createNewOrgQuick') - .input(CreateQuickOrgSchema().inputSchema) - .mutation(async ({ ctx, input }) => { - try { - const inputData = { - actorId: ctx.session.user.id, - operation: 'CREATE', - data: input, - } satisfies CreateQuickOrgInput - - const record = CreateQuickOrgSchema().dataParser.parse(inputData) - - const result = await ctx.prisma.organization.create(record) - - return result - } catch (error) { - handleError(error) - } - }), - createNewSuggestion: protectedProcedure - .input(CreateOrgSuggestionSchema().inputSchema) - .mutation(async ({ ctx, input }) => { - try { - const inputData = { - actorId: ctx.session.user.id, - operation: 'CREATE', - data: input, - } satisfies CreateOrgSuggestionInput - - const record = CreateOrgSuggestionSchema().dataParser.parse(inputData) - const result = await ctx.prisma.suggestion.create(record) - return result - } catch (error) { - handleError(error) - } - }), - attachAttribute: permissionedProcedure('attachOrgAttributes') - .input(AttachOrgAttribute().inputSchema) - .mutation(async ({ ctx, input }) => { - const inputData = { actorId: ctx.session.user.id, operation: 'LINK', data: input } - const { translationKey, freeText, attributeSupplement, organizationAttribute, auditLogs } = - AttachOrgAttribute().dataParser.parse(inputData) - - const result = await ctx.prisma.$transaction(async (tx) => { - const tKey = translationKey ? await tx.translationKey.create(translationKey) : undefined - const fText = freeText ? await tx.freeText.create(freeText) : undefined - const aSupp = attributeSupplement - ? await tx.attributeSupplement.create(attributeSupplement) - : undefined - const attrLink = await tx.organizationAttribute.create(organizationAttribute) - const logs = await tx.auditLog.createMany({ data: auditLogs, skipDuplicates: true }) - return { - translationKey: tKey, - freeText: fText, - attributeSupplement: aSupp, - organizationAttribute: attrLink, - auditLog: logs, - } - }) - return result - }), -}) diff --git a/packages/api/router/organization/queries.ts b/packages/api/router/organization/queries.ts deleted file mode 100644 index 4c0870b20e..0000000000 --- a/packages/api/router/organization/queries.ts +++ /dev/null @@ -1,148 +0,0 @@ -/* eslint-disable @typescript-eslint/consistent-type-imports */ -import { defineRouter, protectedProcedure, publicProcedure } from '~api/lib/trpc' - -import * as schema from './schemas' - -type OrgQueryHandlerCache = { - getById: typeof import('./query.getById.handler').getById - getBySlug: typeof import('./query.getBySlug.handler').getBySlug - getIdFromSlug: typeof import('./query.getIdFromSlug.handler').getIdFromSlug - searchName: typeof import('./query.searchName.handler').searchName - searchDistance: typeof import('./query.searchDistance.handler').searchDistance - getNameFromSlug: typeof import('./query.getNameFromSlug.handler').getNameFromSlug - isSaved: typeof import('./query.isSaved.handler').isSaved - suggestionOptions: typeof import('./query.suggestionOptions.handler').suggestionOptions - checkForExisting: typeof import('./query.checkForExisting.handler').checkForExisting - generateSlug: typeof import('./query.generateSlug.handler').generateSlug - forOrgPage: typeof import('./query.forOrgPage.handler').forOrgPage - forLocationPage: typeof import('./query.forLocationPage.handler').forLocationPage - slugRedirect: typeof import('./query.slugRedirect.handler').slugRedirect - getIntlCrisis: typeof import('./query.getIntlCrisis.handler').getIntlCrisis - getNatlCrisis: typeof import('./query.getNatlCrisis.handler').getNatlCrisis -} - -const HandlerCache: Partial = {} - -export const queries = defineRouter({ - getById: publicProcedure.input(schema.ZGetByIdSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.getById) - HandlerCache.getById = await import('./query.getById.handler').then((mod) => mod.getById) - - if (!HandlerCache.getById) throw new Error('Failed to load handler') - return HandlerCache.getById({ ctx, input }) - }), - getBySlug: publicProcedure.input(schema.ZGetBySlugSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.getBySlug) - HandlerCache.getBySlug = await import('./query.getBySlug.handler').then((mod) => mod.getBySlug) - - if (!HandlerCache.getBySlug) throw new Error('Failed to load handler') - return HandlerCache.getBySlug({ ctx, input }) - }), - getIdFromSlug: publicProcedure.input(schema.ZGetIdFromSlugSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.getIdFromSlug) - HandlerCache.getIdFromSlug = await import('./query.getIdFromSlug.handler').then( - (mod) => mod.getIdFromSlug - ) - - if (!HandlerCache.getIdFromSlug) throw new Error('Failed to load handler') - return HandlerCache.getIdFromSlug({ ctx, input }) - }), - searchName: publicProcedure.input(schema.ZSearchNameSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.searchName) - HandlerCache.searchName = await import('./query.searchName.handler').then((mod) => mod.searchName) - - if (!HandlerCache.searchName) throw new Error('Failed to load handler') - return HandlerCache.searchName({ ctx, input }) - }), - searchDistance: publicProcedure.input(schema.ZSearchDistanceSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.searchDistance) - HandlerCache.searchDistance = await import('./query.searchDistance.handler').then( - (mod) => mod.searchDistance - ) - - if (!HandlerCache.searchDistance) throw new Error('Failed to load handler') - return HandlerCache.searchDistance({ ctx, input }) - }), - getNameFromSlug: publicProcedure.input(schema.ZGetNameFromSlugSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.getNameFromSlug) - HandlerCache.getNameFromSlug = await import('./query.getNameFromSlug.handler').then( - (mod) => mod.getNameFromSlug - ) - - if (!HandlerCache.getNameFromSlug) throw new Error('Failed to load handler') - return HandlerCache.getNameFromSlug({ ctx, input }) - }), - isSaved: publicProcedure.input(schema.ZIsSavedSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.isSaved) - HandlerCache.isSaved = await import('./query.isSaved.handler').then((mod) => mod.isSaved) - - if (!HandlerCache.isSaved) throw new Error('Failed to load handler') - return HandlerCache.isSaved({ ctx, input }) - }), - suggestionOptions: publicProcedure.query(async ({ ctx }) => { - if (!HandlerCache.suggestionOptions) - HandlerCache.suggestionOptions = await import('./query.suggestionOptions.handler').then( - (mod) => mod.suggestionOptions - ) - - if (!HandlerCache.suggestionOptions) throw new Error('Failed to load handler') - return HandlerCache.suggestionOptions() - }), - checkForExisting: publicProcedure.input(schema.ZCheckForExistingSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.checkForExisting) - HandlerCache.checkForExisting = await import('./query.checkForExisting.handler').then( - (mod) => mod.checkForExisting - ) - - if (!HandlerCache.checkForExisting) throw new Error('Failed to load handler') - return HandlerCache.checkForExisting({ ctx, input }) - }), - generateSlug: protectedProcedure.input(schema.ZGenerateSlugSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.generateSlug) - HandlerCache.generateSlug = await import('./query.generateSlug.handler').then((mod) => mod.generateSlug) - - if (!HandlerCache.generateSlug) throw new Error('Failed to load handler') - return HandlerCache.generateSlug({ ctx, input }) - }), - forOrgPage: publicProcedure.input(schema.ZForOrgPageSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.forOrgPage) - HandlerCache.forOrgPage = await import('./query.forOrgPage.handler').then((mod) => mod.forOrgPage) - - if (!HandlerCache.forOrgPage) throw new Error('Failed to load handler') - return HandlerCache.forOrgPage({ ctx, input }) - }), - forLocationPage: publicProcedure.input(schema.ZForLocationPageSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.forLocationPage) - HandlerCache.forLocationPage = await import('./query.forLocationPage.handler').then( - (mod) => mod.forLocationPage - ) - - if (!HandlerCache.forLocationPage) throw new Error('Failed to load handler') - return HandlerCache.forLocationPage({ ctx, input }) - }), - slugRedirect: publicProcedure.input(schema.ZSlugRedirectSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.slugRedirect) - HandlerCache.slugRedirect = await import('./query.slugRedirect.handler').then((mod) => mod.slugRedirect) - - if (!HandlerCache.slugRedirect) throw new Error('Failed to load handler') - return HandlerCache.slugRedirect({ ctx, input }) - }), - getIntlCrisis: publicProcedure.input(schema.ZGetIntlCrisisSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.getIntlCrisis) - HandlerCache.getIntlCrisis = await import('./query.getIntlCrisis.handler').then( - (mod) => mod.getIntlCrisis - ) - - if (!HandlerCache.getIntlCrisis) throw new Error('Failed to load handler') - return HandlerCache.getIntlCrisis({ ctx, input }) - }), - getNatlCrisis: publicProcedure.input(schema.ZGetNatlCrisisSchema).query(async ({ ctx, input }) => { - if (!HandlerCache.getNatlCrisis) - HandlerCache.getNatlCrisis = await import('./query.getNatlCrisis.handler').then( - (mod) => mod.getNatlCrisis - ) - - if (!HandlerCache.getNatlCrisis) throw new Error('Failed to load handler') - return HandlerCache.getNatlCrisis({ ctx, input }) - }), -}) diff --git a/packages/api/router/organization/schemas.ts b/packages/api/router/organization/schemas.ts index 62bd57f2c1..738fc286ab 100644 --- a/packages/api/router/organization/schemas.ts +++ b/packages/api/router/organization/schemas.ts @@ -1,4 +1,7 @@ // codegen:start {preset: barrel, include: ./*.schema.ts} +export * from './mutation.attachAttribute.schema' +export * from './mutation.createNewQuick.schema' +export * from './mutation.createNewSuggestion.schema' export * from './query.checkForExisting.schema' export * from './query.forLocationPage.schema' export * from './query.forOrgPage.schema' From 006ada162a6533953bea811203e1ffc46f4a914d Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Tue, 8 Aug 2023 14:59:47 -0400 Subject: [PATCH 38/63] update schema, add geo autoupdate --- .../migration.sql | 5 +++++ .../migration.sql | 18 ++++++++++++++++++ packages/db/prisma/schema.prisma | 4 +++- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 packages/db/prisma/migrations/20230808183154_city_level_mapping/migration.sql create mode 100644 packages/db/prisma/migrations/20230808183451_autoupdate_geo/migration.sql diff --git a/packages/db/prisma/migrations/20230808183154_city_level_mapping/migration.sql b/packages/db/prisma/migrations/20230808183154_city_level_mapping/migration.sql new file mode 100644 index 0000000000..2814ff1443 --- /dev/null +++ b/packages/db/prisma/migrations/20230808183154_city_level_mapping/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "OrgLocation" + ADD COLUMN "mapCityOnly" BOOLEAN, + ALTER COLUMN "street1" DROP NOT NULL; + diff --git a/packages/db/prisma/migrations/20230808183451_autoupdate_geo/migration.sql b/packages/db/prisma/migrations/20230808183451_autoupdate_geo/migration.sql new file mode 100644 index 0000000000..8c94e0f2dc --- /dev/null +++ b/packages/db/prisma/migrations/20230808183451_autoupdate_geo/migration.sql @@ -0,0 +1,18 @@ +CREATE OR REPLACE FUNCTION update_geo_point() + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + NEW.geo := st_point(NEW.longitude, NEW.latitude, 4326); + NEW."geoJSON" := ST_AsGeoJSON(NEW.geo); + NEW."geoWKT" := ST_AsText(NEW.geo); + RETURN NEW; +END; +$$; + +CREATE OR REPLACE TRIGGER update_geo_trigger + BEFORE INSERT OR UPDATE OF latitude, + longitude ON "OrgLocation" + FOR EACH ROW + EXECUTE FUNCTION update_geo_point(); + diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 555ec73360..e4078bddaa 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -624,7 +624,7 @@ model OrgLocation { id String @id @default(cuid()) legacyId String? @unique /// old ID from MongoDB name String? - street1 String + street1 String? street2 String? city String postCode String? @@ -632,6 +632,8 @@ model OrgLocation { mailOnly Boolean? + mapCityOnly Boolean? + description FreeText? @relation(fields: [descriptionId], references: [id], onDelete: SetNull, onUpdate: Cascade) descriptionId String? @unique From f84f1c9eb29541ac4d213eacf6ee41cb20898afb Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Tue, 8 Aug 2023 22:31:04 -0400 Subject: [PATCH 39/63] add marker icon, minify icon list --- packages/ui/icon/buildIcons.ts | 11 +- packages/ui/icon/iconCollection.ts | 3 + packages/ui/icon/iconList.ts | 2115 +--------------------------- packages/ui/package.json | 1 + pnpm-lock.yaml | 9 + 5 files changed, 17 insertions(+), 2122 deletions(-) diff --git a/packages/ui/icon/buildIcons.ts b/packages/ui/icon/buildIcons.ts index 6af59b8be8..27e02ef13e 100644 --- a/packages/ui/icon/buildIcons.ts +++ b/packages/ui/icon/buildIcons.ts @@ -1,6 +1,5 @@ /* eslint-disable import/no-unused-modules */ import { listIcons } from '@iconify/react' -import prettier from 'prettier' import { writeFileSync } from 'fs' @@ -9,17 +8,11 @@ import { loadIcons } from './iconCollection' loadIcons() const generate = async () => { - const prettierOpts = (await prettier.resolveConfig(__dirname)) ?? undefined - const iconList = `// generated file - do not modify directly\nexport const iconList = ${JSON.stringify( + const iconList = `// generated file - do not modify directly\n// prettier-ignore\nexport const iconList = ${JSON.stringify( listIcons() )} as const` - const formattedOutput = await prettier.format(iconList, { - ...prettierOpts, - parser: 'typescript', - }) - - writeFileSync('./icon/iconList.ts', formattedOutput) + writeFileSync('./icon/iconList.ts', iconList) console.info(`Generated Icon List with ${listIcons().length} icons`) } diff --git a/packages/ui/icon/iconCollection.ts b/packages/ui/icon/iconCollection.ts index 3da19af269..82b22a1fd5 100644 --- a/packages/ui/icon/iconCollection.ts +++ b/packages/ui/icon/iconCollection.ts @@ -3,6 +3,7 @@ import { addCollection } from '@iconify/react' import { getIcons } from '@iconify/utils' const { icons: carbonIcons } = require('@iconify-json/carbon') +const { icons: mdiIcons } = require('@iconify-json/mdi') const { icons: phIcons } = require('@iconify-json/ph') const { icons: simpleIcons } = require('@iconify-json/simple-icons') @@ -14,8 +15,10 @@ const { icons: simpleIcons } = require('@iconify-json/simple-icons') export const loadIcons = () => { const iconsSimple = getIcons(simpleIcons, ['tiktok']) const iconsPh = getIcons(phIcons, ['map-pin-fill']) + const iconsMdi = getIcons(mdiIcons, ['map-marker']) addCollection(carbonIcons) if (iconsSimple !== null) addCollection(iconsSimple) if (iconsPh !== null) addCollection(iconsPh) + if (iconsMdi !== null) addCollection(iconsMdi) } diff --git a/packages/ui/icon/iconList.ts b/packages/ui/icon/iconList.ts index 12edbe0830..701a4b7aca 100644 --- a/packages/ui/icon/iconList.ts +++ b/packages/ui/icon/iconList.ts @@ -1,2114 +1,3 @@ // generated file - do not modify directly -export const iconList = [ - 'carbon:3d-cursor', - 'carbon:3d-cursor-alt', - 'carbon:3d-curve-auto-colon', - 'carbon:3d-curve-auto-vessels', - 'carbon:3d-curve-manual', - 'carbon:3d-ica', - 'carbon:3d-mpr-toggle', - 'carbon:3d-print-mesh', - 'carbon:3d-software', - 'carbon:3rd-party-connected', - 'carbon:4k', - 'carbon:4k-filled', - 'carbon:accessibility', - 'carbon:accessibility-alt', - 'carbon:accessibility-color', - 'carbon:accessibility-color-filled', - 'carbon:account', - 'carbon:accumulation-ice', - 'carbon:accumulation-precipitation', - 'carbon:accumulation-rain', - 'carbon:accumulation-snow', - 'carbon:activity', - 'carbon:add', - 'carbon:add-alt', - 'carbon:add-comment', - 'carbon:add-filled', - 'carbon:agriculture-analytics', - 'carbon:ai-results', - 'carbon:ai-results-high', - 'carbon:ai-results-low', - 'carbon:ai-results-medium', - 'carbon:ai-results-urgent', - 'carbon:ai-results-very-high', - 'carbon:ai-status', - 'carbon:ai-status-complete', - 'carbon:ai-status-failed', - 'carbon:ai-status-in-progress', - 'carbon:ai-status-queued', - 'carbon:ai-status-rejected', - 'carbon:airline-digital-gate', - 'carbon:airline-manage-gates', - 'carbon:airline-passenger-care', - 'carbon:airline-rapid-board', - 'carbon:airplay', - 'carbon:airplay-filled', - 'carbon:airport-01', - 'carbon:airport-02', - 'carbon:airport-location', - 'carbon:alarm', - 'carbon:alarm-add', - 'carbon:alarm-subtract', - 'carbon:align-box-bottom-center', - 'carbon:align-box-bottom-left', - 'carbon:align-box-bottom-right', - 'carbon:align-box-middle-center', - 'carbon:align-box-middle-left', - 'carbon:align-box-middle-right', - 'carbon:align-box-top-center', - 'carbon:align-box-top-left', - 'carbon:align-box-top-right', - 'carbon:align-horizontal-center', - 'carbon:align-horizontal-left', - 'carbon:align-horizontal-right', - 'carbon:align-vertical-bottom', - 'carbon:align-vertical-center', - 'carbon:align-vertical-top', - 'carbon:analytics', - 'carbon:analytics-custom', - 'carbon:analytics-reference', - 'carbon:angle', - 'carbon:annotation-visibility', - 'carbon:aperture', - 'carbon:api', - 'carbon:api-1', - 'carbon:app', - 'carbon:app-connectivity', - 'carbon:apple', - 'carbon:apple-dash', - 'carbon:application', - 'carbon:application-mobile', - 'carbon:application-virtual', - 'carbon:application-web', - 'carbon:apps', - 'carbon:archive', - 'carbon:area', - 'carbon:area-custom', - 'carbon:arrival', - 'carbon:arrow-annotation', - 'carbon:arrow-down', - 'carbon:arrow-down-left', - 'carbon:arrow-down-right', - 'carbon:arrow-left', - 'carbon:arrow-right', - 'carbon:arrow-shift-down', - 'carbon:arrow-up', - 'carbon:arrow-up-left', - 'carbon:arrow-up-right', - 'carbon:arrows-horizontal', - 'carbon:arrows-vertical', - 'carbon:asleep', - 'carbon:asleep-filled', - 'carbon:assembly', - 'carbon:assembly-cluster', - 'carbon:assembly-reference', - 'carbon:asset', - 'carbon:asset-confirm', - 'carbon:asset-digital-twin', - 'carbon:asset-view', - 'carbon:asterisk', - 'carbon:at', - 'carbon:attachment', - 'carbon:audio-console', - 'carbon:augmented-reality', - 'carbon:auto-scroll', - 'carbon:automatic', - 'carbon:autoscaling', - 'carbon:awake', - 'carbon:badge', - 'carbon:baggage-claim', - 'carbon:bar', - 'carbon:barcode', - 'carbon:bare-metal-server', - 'carbon:bare-metal-server-01', - 'carbon:bare-metal-server-02', - 'carbon:barrier', - 'carbon:basketball', - 'carbon:bastion-host', - 'carbon:bat', - 'carbon:batch-job', - 'carbon:batch-job-step', - 'carbon:battery-charging', - 'carbon:battery-empty', - 'carbon:battery-full', - 'carbon:battery-half', - 'carbon:battery-low', - 'carbon:battery-quarter', - 'carbon:bee', - 'carbon:bee-bat', - 'carbon:beta', - 'carbon:bicycle', - 'carbon:binoculars', - 'carbon:bloch-sphere', - 'carbon:block-storage', - 'carbon:block-storage-alt', - 'carbon:blockchain', - 'carbon:blog', - 'carbon:bluetooth', - 'carbon:bluetooth-off', - 'carbon:book', - 'carbon:bookmark', - 'carbon:bookmark-add', - 'carbon:bookmark-filled', - 'carbon:boolean', - 'carbon:boot', - 'carbon:boot-volume', - 'carbon:boot-volume-alt', - 'carbon:border-bottom', - 'carbon:border-full', - 'carbon:border-left', - 'carbon:border-none', - 'carbon:border-right', - 'carbon:border-top', - 'carbon:bot', - 'carbon:bottles-01', - 'carbon:bottles-01-dash', - 'carbon:bottles-02', - 'carbon:bottles-02-dash', - 'carbon:bottles-container', - 'carbon:box', - 'carbon:box-extra-large', - 'carbon:box-large', - 'carbon:box-medium', - 'carbon:box-plot', - 'carbon:box-small', - 'carbon:branch', - 'carbon:breaking-change', - 'carbon:brightness-contrast', - 'carbon:bring-forward', - 'carbon:bring-to-front', - 'carbon:brush-freehand', - 'carbon:brush-polygon', - 'carbon:build-tool', - 'carbon:building', - 'carbon:building-insights-1', - 'carbon:building-insights-2', - 'carbon:building-insights-3', - 'carbon:bullhorn', - 'carbon:buoy', - 'carbon:bus', - 'carbon:button-centered', - 'carbon:button-flush-left', - 'carbon:cabin-care', - 'carbon:cabin-care-alert', - 'carbon:cabin-care-alt', - 'carbon:cad', - 'carbon:cafe', - 'carbon:calculation', - 'carbon:calculation-alt', - 'carbon:calculator', - 'carbon:calculator-check', - 'carbon:calendar', - 'carbon:calendar-add', - 'carbon:calendar-add-alt', - 'carbon:calendar-heat-map', - 'carbon:calendar-settings', - 'carbon:calendar-tools', - 'carbon:calibrate', - 'carbon:camera', - 'carbon:camera-action', - 'carbon:campsite', - 'carbon:car', - 'carbon:car-front', - 'carbon:carbon', - 'carbon:carbon-accounting', - 'carbon:carbon-for-ibm-dotcom', - 'carbon:carbon-for-ibm-product', - 'carbon:carbon-ui-builder', - 'carbon:caret-down', - 'carbon:caret-left', - 'carbon:caret-right', - 'carbon:caret-sort', - 'carbon:caret-sort-down', - 'carbon:caret-sort-up', - 'carbon:caret-up', - 'carbon:carousel-horizontal', - 'carbon:carousel-vertical', - 'carbon:catalog', - 'carbon:catalog-publish', - 'carbon:categories', - 'carbon:category', - 'carbon:category-add', - 'carbon:category-and', - 'carbon:category-new', - 'carbon:category-new-each', - 'carbon:ccx', - 'carbon:cd-archive', - 'carbon:cd-create-archive', - 'carbon:cd-create-exchange', - 'carbon:cda', - 'carbon:cell-tower', - 'carbon:center-circle', - 'carbon:center-square', - 'carbon:center-to-fit', - 'carbon:certificate', - 'carbon:certificate-check', - 'carbon:change-catalog', - 'carbon:character-decimal', - 'carbon:character-fraction', - 'carbon:character-integer', - 'carbon:character-lower-case', - 'carbon:character-negative-number', - 'carbon:character-patterns', - 'carbon:character-sentence-case', - 'carbon:character-upper-case', - 'carbon:character-whole-number', - 'carbon:charging-station', - 'carbon:charging-station-filled', - 'carbon:chart-3d', - 'carbon:chart-area', - 'carbon:chart-area-smooth', - 'carbon:chart-area-stepper', - 'carbon:chart-average', - 'carbon:chart-bar', - 'carbon:chart-bar-floating', - 'carbon:chart-bar-overlay', - 'carbon:chart-bar-stacked', - 'carbon:chart-bar-target', - 'carbon:chart-bubble', - 'carbon:chart-bubble-packed', - 'carbon:chart-bullet', - 'carbon:chart-candlestick', - 'carbon:chart-cluster-bar', - 'carbon:chart-column', - 'carbon:chart-column-floating', - 'carbon:chart-column-target', - 'carbon:chart-combo', - 'carbon:chart-combo-stacked', - 'carbon:chart-custom', - 'carbon:chart-error-bar', - 'carbon:chart-error-bar-alt', - 'carbon:chart-evaluation', - 'carbon:chart-high-low', - 'carbon:chart-histogram', - 'carbon:chart-line', - 'carbon:chart-line-data', - 'carbon:chart-line-smooth', - 'carbon:chart-logistic-regression', - 'carbon:chart-marimekko', - 'carbon:chart-maximum', - 'carbon:chart-median', - 'carbon:chart-minimum', - 'carbon:chart-multi-line', - 'carbon:chart-multitype', - 'carbon:chart-network', - 'carbon:chart-parallel', - 'carbon:chart-pie', - 'carbon:chart-point', - 'carbon:chart-population', - 'carbon:chart-radar', - 'carbon:chart-radial', - 'carbon:chart-relationship', - 'carbon:chart-ring', - 'carbon:chart-river', - 'carbon:chart-rose', - 'carbon:chart-scatter', - 'carbon:chart-spiral', - 'carbon:chart-stacked', - 'carbon:chart-stepper', - 'carbon:chart-sunburst', - 'carbon:chart-t-sne', - 'carbon:chart-treemap', - 'carbon:chart-venn-diagram', - 'carbon:chart-violin-plot', - 'carbon:chart-waterfall', - 'carbon:chart-win-loss', - 'carbon:chat', - 'carbon:chat-bot', - 'carbon:chat-launch', - 'carbon:chat-off', - 'carbon:chat-operational', - 'carbon:checkbox', - 'carbon:checkbox-checked', - 'carbon:checkbox-checked-filled', - 'carbon:checkbox-indeterminate', - 'carbon:checkbox-indeterminate-filled', - 'carbon:checkbox-undeterminate-filled', - 'carbon:checkmark', - 'carbon:checkmark-filled', - 'carbon:checkmark-filled-error', - 'carbon:checkmark-filled-warning', - 'carbon:checkmark-outline', - 'carbon:checkmark-outline-error', - 'carbon:checkmark-outline-warning', - 'carbon:chemistry', - 'carbon:chemistry-reference', - 'carbon:chevron-down', - 'carbon:chevron-left', - 'carbon:chevron-mini', - 'carbon:chevron-right', - 'carbon:chevron-sort', - 'carbon:chevron-sort-down', - 'carbon:chevron-sort-up', - 'carbon:chevron-up', - 'carbon:chip', - 'carbon:choices', - 'carbon:choose-item', - 'carbon:choropleth-map', - 'carbon:cics-cmas', - 'carbon:cics-db2-connection', - 'carbon:cics-explorer', - 'carbon:cics-program', - 'carbon:cics-region', - 'carbon:cics-region-alt', - 'carbon:cics-region-routing', - 'carbon:cics-region-target', - 'carbon:cics-sit', - 'carbon:cics-sit-overrides', - 'carbon:cics-system-group', - 'carbon:cics-transaction-server-zos', - 'carbon:cics-wui-region', - 'carbon:cicsplex', - 'carbon:circle-dash', - 'carbon:circle-filled', - 'carbon:circle-measurement', - 'carbon:circle-packing', - 'carbon:circle-solid', - 'carbon:circuit-composer', - 'carbon:classification', - 'carbon:classifier-language', - 'carbon:clean', - 'carbon:close', - 'carbon:close-filled', - 'carbon:close-outline', - 'carbon:closed-caption', - 'carbon:closed-caption-alt', - 'carbon:closed-caption-filled', - 'carbon:cloud', - 'carbon:cloud-alerting', - 'carbon:cloud-app', - 'carbon:cloud-auditing', - 'carbon:cloud-ceiling', - 'carbon:cloud-data-ops', - 'carbon:cloud-download', - 'carbon:cloud-foundry-1', - 'carbon:cloud-foundry-2', - 'carbon:cloud-logging', - 'carbon:cloud-monitoring', - 'carbon:cloud-offline', - 'carbon:cloud-registry', - 'carbon:cloud-satellite', - 'carbon:cloud-satellite-config', - 'carbon:cloud-satellite-link', - 'carbon:cloud-satellite-services', - 'carbon:cloud-service-management', - 'carbon:cloud-services', - 'carbon:cloud-upload', - 'carbon:cloudy', - 'carbon:cobb-angle', - 'carbon:code', - 'carbon:code-hide', - 'carbon:code-reference', - 'carbon:code-signing-service', - 'carbon:cognitive', - 'carbon:collaborate', - 'carbon:collapse-all', - 'carbon:collapse-categories', - 'carbon:color-palette', - 'carbon:color-switch', - 'carbon:column', - 'carbon:column-delete', - 'carbon:column-dependency', - 'carbon:column-insert', - 'carbon:commit', - 'carbon:communication-unified', - 'carbon:compare', - 'carbon:compass', - 'carbon:composer-edit', - 'carbon:concept', - 'carbon:condition-point', - 'carbon:condition-wait-point', - 'carbon:connect', - 'carbon:connect-recursive', - 'carbon:connect-reference', - 'carbon:connect-source', - 'carbon:connect-target', - 'carbon:connection-receive', - 'carbon:connection-send', - 'carbon:connection-signal', - 'carbon:connection-signal-off', - 'carbon:connection-two-way', - 'carbon:construction', - 'carbon:container-registry', - 'carbon:container-services', - 'carbon:container-software', - 'carbon:content-delivery-network', - 'carbon:content-view', - 'carbon:continue', - 'carbon:continue-filled', - 'carbon:continuous-deployment', - 'carbon:continuous-integration', - 'carbon:contour-draw', - 'carbon:contour-edit', - 'carbon:contour-finding', - 'carbon:contrast', - 'carbon:convert-to-cloud', - 'carbon:cookie', - 'carbon:copy', - 'carbon:copy-file', - 'carbon:copy-link', - 'carbon:corn', - 'carbon:corner', - 'carbon:coronavirus', - 'carbon:cost', - 'carbon:cost-total', - 'carbon:cough', - 'carbon:course', - 'carbon:covariate', - 'carbon:credentials', - 'carbon:crop', - 'carbon:crop-growth', - 'carbon:crop-health', - 'carbon:cross-reference', - 'carbon:cross-tab', - 'carbon:crossroads', - 'carbon:crowd-report', - 'carbon:crowd-report-filled', - 'carbon:csv', - 'carbon:cu1', - 'carbon:cu3', - 'carbon:cube', - 'carbon:cube-view', - 'carbon:currency', - 'carbon:currency-baht', - 'carbon:currency-dollar', - 'carbon:currency-euro', - 'carbon:currency-lira', - 'carbon:currency-pound', - 'carbon:currency-ruble', - 'carbon:currency-rupee', - 'carbon:currency-shekel', - 'carbon:currency-won', - 'carbon:currency-yen', - 'carbon:cursor-1', - 'carbon:cursor-2', - 'carbon:customer-service', - 'carbon:cut', - 'carbon:cut-in-half', - 'carbon:cut-out', - 'carbon:cy', - 'carbon:cyclist', - 'carbon:cz', - 'carbon:dashboard', - 'carbon:dashboard-reference', - 'carbon:data-1', - 'carbon:data-2', - 'carbon:data-accessor', - 'carbon:data-backup', - 'carbon:data-base', - 'carbon:data-base-alt', - 'carbon:data-bin', - 'carbon:data-blob', - 'carbon:data-categorical', - 'carbon:data-center', - 'carbon:data-check', - 'carbon:data-class', - 'carbon:data-collection', - 'carbon:data-connected', - 'carbon:data-definition', - 'carbon:data-diode', - 'carbon:data-enrichment', - 'carbon:data-enrichment-add', - 'carbon:data-error', - 'carbon:data-format', - 'carbon:data-player', - 'carbon:data-quality-definition', - 'carbon:data-reference', - 'carbon:data-refinery', - 'carbon:data-refinery-reference', - 'carbon:data-regular', - 'carbon:data-set', - 'carbon:data-share', - 'carbon:data-structured', - 'carbon:data-table', - 'carbon:data-table-reference', - 'carbon:data-unstructured', - 'carbon:data-view', - 'carbon:data-view-alt', - 'carbon:data-vis-1', - 'carbon:data-vis-2', - 'carbon:data-vis-3', - 'carbon:data-vis-4', - 'carbon:data-volume', - 'carbon:data-volume-alt', - 'carbon:database-datastax', - 'carbon:database-elastic', - 'carbon:database-enterprise-db2', - 'carbon:database-etcd', - 'carbon:database-mongodb', - 'carbon:database-postgresql', - 'carbon:database-rabbit', - 'carbon:database-redis', - 'carbon:datastore', - 'carbon:db2-buffer-pool', - 'carbon:db2-data-sharing-group', - 'carbon:db2-database', - 'carbon:debug', - 'carbon:decision-tree', - 'carbon:delivery', - 'carbon:delivery-add', - 'carbon:delivery-parcel', - 'carbon:delivery-truck', - 'carbon:denominate', - 'carbon:departure', - 'carbon:deploy', - 'carbon:deploy-rules', - 'carbon:deployment-pattern', - 'carbon:deployment-policy', - 'carbon:deployment-unit-data', - 'carbon:deployment-unit-execution', - 'carbon:deployment-unit-installation', - 'carbon:deployment-unit-presentation', - 'carbon:deployment-unit-technical-data', - 'carbon:deployment-unit-technical-execution', - 'carbon:deployment-unit-technical-installation', - 'carbon:deployment-unit-technical-presentation', - 'carbon:desk-adjustable', - 'carbon:development', - 'carbon:devices', - 'carbon:dew-point', - 'carbon:dew-point-filled', - 'carbon:diagram', - 'carbon:diagram-reference', - 'carbon:dicom-6000', - 'carbon:dicom-overlay', - 'carbon:direct-link', - 'carbon:direction-bear-right-01', - 'carbon:direction-bear-right-01-filled', - 'carbon:direction-bear-right-02', - 'carbon:direction-bear-right-02-filled', - 'carbon:direction-curve', - 'carbon:direction-curve-filled', - 'carbon:direction-fork', - 'carbon:direction-fork-filled', - 'carbon:direction-loop-left', - 'carbon:direction-loop-left-filled', - 'carbon:direction-loop-right', - 'carbon:direction-loop-right-filled', - 'carbon:direction-merge', - 'carbon:direction-merge-filled', - 'carbon:direction-right-01', - 'carbon:direction-right-01-filled', - 'carbon:direction-right-02', - 'carbon:direction-right-02-filled', - 'carbon:direction-rotary-first-right', - 'carbon:direction-rotary-first-right-filled', - 'carbon:direction-rotary-right', - 'carbon:direction-rotary-right-filled', - 'carbon:direction-rotary-straight', - 'carbon:direction-rotary-straight-filled', - 'carbon:direction-sharp-turn', - 'carbon:direction-sharp-turn-filled', - 'carbon:direction-straight', - 'carbon:direction-straight-filled', - 'carbon:direction-straight-right', - 'carbon:direction-straight-right-filled', - 'carbon:direction-u-turn', - 'carbon:direction-u-turn-filled', - 'carbon:directory-domain', - 'carbon:distribute-horizontal-center', - 'carbon:distribute-horizontal-left', - 'carbon:distribute-horizontal-right', - 'carbon:distribute-vertical-bottom', - 'carbon:distribute-vertical-center', - 'carbon:distribute-vertical-top', - 'carbon:dna', - 'carbon:dns-services', - 'carbon:doc', - 'carbon:document', - 'carbon:document-add', - 'carbon:document-attachment', - 'carbon:document-audio', - 'carbon:document-blank', - 'carbon:document-download', - 'carbon:document-epdf', - 'carbon:document-export', - 'carbon:document-horizontal', - 'carbon:document-import', - 'carbon:document-multiple-01', - 'carbon:document-multiple-02', - 'carbon:document-pdf', - 'carbon:document-preliminary', - 'carbon:document-protected', - 'carbon:document-security', - 'carbon:document-sentiment', - 'carbon:document-signed', - 'carbon:document-sketch', - 'carbon:document-subtract', - 'carbon:document-tasks', - 'carbon:document-unknown', - 'carbon:document-unprotected', - 'carbon:document-vertical', - 'carbon:document-video', - 'carbon:document-view', - 'carbon:document-word-processor', - 'carbon:document-word-processor-reference', - 'carbon:dog-walker', - 'carbon:dot-mark', - 'carbon:double-integer', - 'carbon:down-to-bottom', - 'carbon:download', - 'carbon:download-study', - 'carbon:drag-horizontal', - 'carbon:drag-vertical', - 'carbon:draggable', - 'carbon:draw', - 'carbon:drill-back', - 'carbon:drill-down', - 'carbon:drill-through', - 'carbon:drink-01', - 'carbon:drink-02', - 'carbon:driver-analysis', - 'carbon:drone', - 'carbon:drone-delivery', - 'carbon:drone-front', - 'carbon:drone-video', - 'carbon:drop-photo', - 'carbon:drop-photo-filled', - 'carbon:drought', - 'carbon:dvr', - 'carbon:earth', - 'carbon:earth-americas', - 'carbon:earth-americas-filled', - 'carbon:earth-europe-africa', - 'carbon:earth-europe-africa-filled', - 'carbon:earth-filled', - 'carbon:earth-southeast-asia', - 'carbon:earth-southeast-asia-filled', - 'carbon:earthquake', - 'carbon:edge-cluster', - 'carbon:edge-device', - 'carbon:edge-enhancement', - 'carbon:edge-enhancement-01', - 'carbon:edge-enhancement-02', - 'carbon:edge-enhancement-03', - 'carbon:edge-node', - 'carbon:edge-node-alt', - 'carbon:edge-service', - 'carbon:edit', - 'carbon:edit-filter', - 'carbon:edit-off', - 'carbon:edt-loop', - 'carbon:education', - 'carbon:email', - 'carbon:email-new', - 'carbon:encryption', - 'carbon:energy-renewable', - 'carbon:enterprise', - 'carbon:equalizer', - 'carbon:erase', - 'carbon:erase-3d', - 'carbon:error', - 'carbon:error-filled', - 'carbon:error-outline', - 'carbon:event', - 'carbon:event-schedule', - 'carbon:events', - 'carbon:events-alt', - 'carbon:exam-mode', - 'carbon:executable-program', - 'carbon:exit', - 'carbon:expand-all', - 'carbon:expand-categories', - 'carbon:explore', - 'carbon:export', - 'carbon:eyedropper', - 'carbon:face-activated', - 'carbon:face-activated-add', - 'carbon:face-activated-filled', - 'carbon:face-add', - 'carbon:face-cool', - 'carbon:face-dissatisfied', - 'carbon:face-dissatisfied-filled', - 'carbon:face-dizzy', - 'carbon:face-dizzy-filled', - 'carbon:face-mask', - 'carbon:face-neutral', - 'carbon:face-neutral-filled', - 'carbon:face-pending', - 'carbon:face-pending-filled', - 'carbon:face-satisfied', - 'carbon:face-satisfied-filled', - 'carbon:face-wink', - 'carbon:face-wink-filled', - 'carbon:factor', - 'carbon:fade', - 'carbon:favorite', - 'carbon:favorite-filled', - 'carbon:favorite-half', - 'carbon:fetch-upload', - 'carbon:fetch-upload-cloud', - 'carbon:file-storage', - 'carbon:filter', - 'carbon:filter-edit', - 'carbon:filter-remove', - 'carbon:filter-reset', - 'carbon:finance', - 'carbon:fingerprint-recognition', - 'carbon:fire', - 'carbon:firewall', - 'carbon:firewall-classic', - 'carbon:fish', - 'carbon:fish-multiple', - 'carbon:fit-to-height', - 'carbon:fit-to-screen', - 'carbon:fit-to-width', - 'carbon:flag', - 'carbon:flag-filled', - 'carbon:flagging-taxi', - 'carbon:flash', - 'carbon:flash-filled', - 'carbon:flash-off', - 'carbon:flash-off-filled', - 'carbon:flight-international', - 'carbon:flight-roster', - 'carbon:flight-schedule', - 'carbon:floating-ip', - 'carbon:flood', - 'carbon:flood-warning', - 'carbon:floorplan', - 'carbon:flow', - 'carbon:flow-connection', - 'carbon:flow-data', - 'carbon:flow-logs-vpc', - 'carbon:flow-modeler', - 'carbon:flow-modeler-reference', - 'carbon:flow-stream', - 'carbon:flow-stream-reference', - 'carbon:fog', - 'carbon:folder', - 'carbon:folder-add', - 'carbon:folder-details', - 'carbon:folder-details-reference', - 'carbon:folder-move-to', - 'carbon:folder-off', - 'carbon:folder-open', - 'carbon:folder-parent', - 'carbon:folder-shared', - 'carbon:folders', - 'carbon:forecast-hail', - 'carbon:forecast-hail-30', - 'carbon:forecast-lightning', - 'carbon:forecast-lightning-30', - 'carbon:fork', - 'carbon:forum', - 'carbon:forward-10', - 'carbon:forward-30', - 'carbon:forward-5', - 'carbon:fragile', - 'carbon:friendship', - 'carbon:fruit-bowl', - 'carbon:function', - 'carbon:function-math', - 'carbon:fusion-blender', - 'carbon:game-console', - 'carbon:game-wireless', - 'carbon:gamification', - 'carbon:gas-station', - 'carbon:gas-station-filled', - 'carbon:gateway', - 'carbon:gateway-api', - 'carbon:gateway-mail', - 'carbon:gateway-public', - 'carbon:gateway-security', - 'carbon:gateway-user-access', - 'carbon:gateway-vpn', - 'carbon:gender-female', - 'carbon:gender-male', - 'carbon:generate-pdf', - 'carbon:gif', - 'carbon:gift', - 'carbon:globe', - 'carbon:gradient', - 'carbon:graphical-data-flow', - 'carbon:grid', - 'carbon:group', - 'carbon:group-access', - 'carbon:group-account', - 'carbon:group-objects', - 'carbon:group-objects-new', - 'carbon:group-objects-save', - 'carbon:group-presentation', - 'carbon:group-resource', - 'carbon:group-security', - 'carbon:growth', - 'carbon:gui', - 'carbon:gui-management', - 'carbon:h', - 'carbon:hail', - 'carbon:hanging-protocol', - 'carbon:harbor', - 'carbon:hardware-security-module', - 'carbon:hashtag', - 'carbon:haze', - 'carbon:haze-night', - 'carbon:hd', - 'carbon:hd-filled', - 'carbon:hdr', - 'carbon:headphones', - 'carbon:headset', - 'carbon:health-cross', - 'carbon:hearing', - 'carbon:heat-map', - 'carbon:heat-map-02', - 'carbon:heat-map-03', - 'carbon:heat-map-stocks', - 'carbon:helicopter', - 'carbon:help', - 'carbon:help-desk', - 'carbon:help-filled', - 'carbon:hinton-plot', - 'carbon:hl7-attributes', - 'carbon:hole-filling', - 'carbon:hole-filling-cursor', - 'carbon:home', - 'carbon:horizontal-view', - 'carbon:hospital', - 'carbon:hospital-bed', - 'carbon:hotel', - 'carbon:hourglass', - 'carbon:html', - 'carbon:html-reference', - 'carbon:http', - 'carbon:humidity', - 'carbon:humidity-alt', - 'carbon:hurricane', - 'carbon:hybrid-networking', - 'carbon:hybrid-networking-alt', - 'carbon:ibm-bluepay', - 'carbon:ibm-cloud', - 'carbon:ibm-cloud-app-id', - 'carbon:ibm-cloud-citrix-daas', - 'carbon:ibm-cloud-continuous-delivery', - 'carbon:ibm-cloud-dedicated-host', - 'carbon:ibm-cloud-direct-link-1-connect', - 'carbon:ibm-cloud-direct-link-1-dedicated', - 'carbon:ibm-cloud-direct-link-1-dedicated-hosting', - 'carbon:ibm-cloud-direct-link-1-exchange', - 'carbon:ibm-cloud-direct-link-2-connect', - 'carbon:ibm-cloud-direct-link-2-dedicated', - 'carbon:ibm-cloud-direct-link-2-dedicated-hosting', - 'carbon:ibm-cloud-event-notification', - 'carbon:ibm-cloud-event-streams', - 'carbon:ibm-cloud-for-education', - 'carbon:ibm-cloud-hsm', - 'carbon:ibm-cloud-hyper-protect-crypto-services', - 'carbon:ibm-cloud-hyper-protect-dbaas', - 'carbon:ibm-cloud-hyper-protect-vs', - 'carbon:ibm-cloud-internet-services', - 'carbon:ibm-cloud-ipsec-vpn', - 'carbon:ibm-cloud-key-protect', - 'carbon:ibm-cloud-kubernetes-service', - 'carbon:ibm-cloud-logging', - 'carbon:ibm-cloud-mass-data-migration', - 'carbon:ibm-cloud-pak-applications', - 'carbon:ibm-cloud-pak-business-automation', - 'carbon:ibm-cloud-pak-data', - 'carbon:ibm-cloud-pak-integration', - 'carbon:ibm-cloud-pak-manta-automated-data-lineage', - 'carbon:ibm-cloud-pak-multicloud-mgmt', - 'carbon:ibm-cloud-pak-netezza', - 'carbon:ibm-cloud-pak-network-automation', - 'carbon:ibm-cloud-pak-security', - 'carbon:ibm-cloud-pak-system', - 'carbon:ibm-cloud-pak-watson-aiops', - 'carbon:ibm-cloud-pal', - 'carbon:ibm-cloud-privileged-access-gateway', - 'carbon:ibm-cloud-projects', - 'carbon:ibm-cloud-resiliency', - 'carbon:ibm-cloud-secrets-manager', - 'carbon:ibm-cloud-security-compliance-center', - 'carbon:ibm-cloud-security-compliance-center-workload-protection', - 'carbon:ibm-cloud-subnets', - 'carbon:ibm-cloud-sysdig-secure', - 'carbon:ibm-cloud-transit-gateway', - 'carbon:ibm-cloud-vpc-endpoints', - 'carbon:ibm-content-services', - 'carbon:ibm-data-replication', - 'carbon:ibm-datastage', - 'carbon:ibm-db2', - 'carbon:ibm-db2-alt', - 'carbon:ibm-match-360', - 'carbon:ibm-mq', - 'carbon:ibm-open-enterprise-languages', - 'carbon:ibm-openshift-container-platform-on-vpc-for-regulated-industries', - 'carbon:ibm-power-vs', - 'carbon:ibm-power-with-vpc', - 'carbon:ibm-private-path-services', - 'carbon:ibm-process-mining', - 'carbon:ibm-sap-on-power', - 'carbon:ibm-secure-infrastructure-on-vpc-for-regulated-industries', - 'carbon:ibm-security', - 'carbon:ibm-security-services', - 'carbon:ibm-telehealth', - 'carbon:ibm-tenet', - 'carbon:ibm-toolchain', - 'carbon:ibm-vsi-on-vpc-for-regulated-industries', - 'carbon:ibm-watson-assistant', - 'carbon:ibm-watson-discovery', - 'carbon:ibm-watson-knowledge-catalog', - 'carbon:ibm-watson-knowledge-studio', - 'carbon:ibm-watson-language-translator', - 'carbon:ibm-watson-machine-learning', - 'carbon:ibm-watson-natural-language-classifier', - 'carbon:ibm-watson-natural-language-understanding', - 'carbon:ibm-watson-openscale', - 'carbon:ibm-watson-orders', - 'carbon:ibm-watson-query', - 'carbon:ibm-watson-speech-to-text', - 'carbon:ibm-watson-studio', - 'carbon:ibm-watson-text-to-speech', - 'carbon:ibm-watson-tone-analyzer', - 'carbon:ibm-z-cloud-mod-stack', - 'carbon:ibm-z-cloud-provisioning', - 'carbon:ibm-z-environments-dev-sec-ops', - 'carbon:ibm-z-os-ai-control-interface', - 'carbon:ibm-z-os-containers', - 'carbon:ibm-z-os-package-manager', - 'carbon:ica-2d', - 'carbon:ice-accretion', - 'carbon:ice-vision', - 'carbon:id', - 'carbon:id-management', - 'carbon:idea', - 'carbon:identification', - 'carbon:image', - 'carbon:image-copy', - 'carbon:image-medical', - 'carbon:image-reference', - 'carbon:image-search', - 'carbon:image-search-alt', - 'carbon:image-service', - 'carbon:import-export', - 'carbon:improve-relevance', - 'carbon:in-progress', - 'carbon:in-progress-error', - 'carbon:in-progress-warning', - 'carbon:incomplete', - 'carbon:incomplete-cancel', - 'carbon:incomplete-error', - 'carbon:incomplete-warning', - 'carbon:increase-level', - 'carbon:industry', - 'carbon:information', - 'carbon:information-disabled', - 'carbon:information-filled', - 'carbon:information-square', - 'carbon:information-square-filled', - 'carbon:infrastructure-classic', - 'carbon:insert', - 'carbon:insert-page', - 'carbon:insert-syntax', - 'carbon:inspection', - 'carbon:instance-bx', - 'carbon:instance-classic', - 'carbon:instance-cx', - 'carbon:instance-mx', - 'carbon:instance-virtual', - 'carbon:integration', - 'carbon:intent-request-active', - 'carbon:intent-request-create', - 'carbon:intent-request-heal', - 'carbon:intent-request-inactive', - 'carbon:intent-request-scale-in', - 'carbon:intent-request-scale-out', - 'carbon:intent-request-uninstall', - 'carbon:intent-request-upgrade', - 'carbon:interactions', - 'carbon:interactive-segmentation-cursor', - 'carbon:intersect', - 'carbon:intrusion-prevention', - 'carbon:inventory-management', - 'carbon:iot-connect', - 'carbon:iot-platform', - 'carbon:iso', - 'carbon:iso-filled', - 'carbon:iso-outline', - 'carbon:join-full', - 'carbon:join-inner', - 'carbon:join-left', - 'carbon:join-outer', - 'carbon:join-right', - 'carbon:jpg', - 'carbon:json', - 'carbon:json-reference', - 'carbon:jump-link', - 'carbon:keep-dry', - 'carbon:keyboard', - 'carbon:keyboard-off', - 'carbon:kubernetes', - 'carbon:label', - 'carbon:language', - 'carbon:laptop', - 'carbon:lasso', - 'carbon:lasso-polygon', - 'carbon:launch', - 'carbon:launch-study-1', - 'carbon:launch-study-2', - 'carbon:launch-study-3', - 'carbon:layers', - 'carbon:legend', - 'carbon:letter-aa', - 'carbon:letter-bb', - 'carbon:letter-cc', - 'carbon:letter-dd', - 'carbon:letter-ee', - 'carbon:letter-ff', - 'carbon:letter-gg', - 'carbon:letter-hh', - 'carbon:letter-ii', - 'carbon:letter-jj', - 'carbon:letter-kk', - 'carbon:letter-ll', - 'carbon:letter-mm', - 'carbon:letter-nn', - 'carbon:letter-oo', - 'carbon:letter-pp', - 'carbon:letter-qq', - 'carbon:letter-rr', - 'carbon:letter-ss', - 'carbon:letter-tt', - 'carbon:letter-uu', - 'carbon:letter-vv', - 'carbon:letter-ww', - 'carbon:letter-xx', - 'carbon:letter-yy', - 'carbon:letter-zz', - 'carbon:license', - 'carbon:license-draft', - 'carbon:license-global', - 'carbon:license-maintenance', - 'carbon:license-maintenance-draft', - 'carbon:license-third-party', - 'carbon:license-third-party-draft', - 'carbon:lifesaver', - 'carbon:light', - 'carbon:light-filled', - 'carbon:lightning', - 'carbon:link', - 'carbon:linux', - 'carbon:linux-alt', - 'carbon:list', - 'carbon:list-boxes', - 'carbon:list-bulleted', - 'carbon:list-checked', - 'carbon:list-checked-mirror', - 'carbon:list-dropdown', - 'carbon:list-numbered', - 'carbon:list-numbered-mirror', - 'carbon:load-balancer-application', - 'carbon:load-balancer-classic', - 'carbon:load-balancer-global', - 'carbon:load-balancer-listener', - 'carbon:load-balancer-local', - 'carbon:load-balancer-network', - 'carbon:load-balancer-pool', - 'carbon:load-balancer-vpc', - 'carbon:location', - 'carbon:location-company', - 'carbon:location-company-filled', - 'carbon:location-current', - 'carbon:location-filled', - 'carbon:location-hazard', - 'carbon:location-hazard-filled', - 'carbon:location-heart', - 'carbon:location-heart-filled', - 'carbon:location-person', - 'carbon:location-person-filled', - 'carbon:location-save', - 'carbon:location-star', - 'carbon:location-star-filled', - 'carbon:locked', - 'carbon:logical-partition', - 'carbon:login', - 'carbon:logo-angular', - 'carbon:logo-ansible-community', - 'carbon:logo-delicious', - 'carbon:logo-digg', - 'carbon:logo-discord', - 'carbon:logo-facebook', - 'carbon:logo-figma', - 'carbon:logo-flickr', - 'carbon:logo-github', - 'carbon:logo-glassdoor', - 'carbon:logo-google', - 'carbon:logo-instagram', - 'carbon:logo-invision', - 'carbon:logo-jupyter', - 'carbon:logo-keybase', - 'carbon:logo-kubernetes', - 'carbon:logo-linkedin', - 'carbon:logo-livestream', - 'carbon:logo-mastodon', - 'carbon:logo-medium', - 'carbon:logo-npm', - 'carbon:logo-openshift', - 'carbon:logo-pinterest', - 'carbon:logo-python', - 'carbon:logo-quora', - 'carbon:logo-r-script', - 'carbon:logo-react', - 'carbon:logo-red-hat-ansible', - 'carbon:logo-sketch', - 'carbon:logo-skype', - 'carbon:logo-slack', - 'carbon:logo-snapchat', - 'carbon:logo-stumbleupon', - 'carbon:logo-svelte', - 'carbon:logo-tumblr', - 'carbon:logo-twitter', - 'carbon:logo-vmware', - 'carbon:logo-vmware-alt', - 'carbon:logo-vue', - 'carbon:logo-wechat', - 'carbon:logo-xing', - 'carbon:logo-yelp', - 'carbon:logo-youtube', - 'carbon:logout', - 'carbon:loop', - 'carbon:mac-command', - 'carbon:mac-option', - 'carbon:mac-shift', - 'carbon:machine-learning', - 'carbon:machine-learning-model', - 'carbon:magic-wand', - 'carbon:magic-wand-filled', - 'carbon:magnify', - 'carbon:mail-all', - 'carbon:mail-reply', - 'carbon:mammogram', - 'carbon:mammogram-stacked', - 'carbon:manage-protection', - 'carbon:managed-solutions', - 'carbon:map', - 'carbon:map-boundary', - 'carbon:map-boundary-vegetation', - 'carbon:map-center', - 'carbon:map-identify', - 'carbon:marine-warning', - 'carbon:math-curve', - 'carbon:matrix', - 'carbon:maximize', - 'carbon:media-cast', - 'carbon:media-library', - 'carbon:media-library-filled', - 'carbon:medication', - 'carbon:medication-alert', - 'carbon:medication-reminder', - 'carbon:menu', - 'carbon:message-queue', - 'carbon:meter', - 'carbon:meter-alt', - 'carbon:microphone', - 'carbon:microphone-filled', - 'carbon:microphone-off', - 'carbon:microphone-off-filled', - 'carbon:microscope', - 'carbon:microservices-1', - 'carbon:microservices-2', - 'carbon:migrate', - 'carbon:migrate-alt', - 'carbon:milestone', - 'carbon:military-camp', - 'carbon:minimize', - 'carbon:misuse', - 'carbon:misuse-alt', - 'carbon:misuse-outline', - 'carbon:mixed-rain-hail', - 'carbon:mobile', - 'carbon:mobile-add', - 'carbon:mobile-audio', - 'carbon:mobile-check', - 'carbon:mobile-download', - 'carbon:mobile-landscape', - 'carbon:mobility-services', - 'carbon:model', - 'carbon:model-alt', - 'carbon:model-builder', - 'carbon:model-builder-reference', - 'carbon:model-reference', - 'carbon:money', - 'carbon:monster', - 'carbon:monument', - 'carbon:moon', - 'carbon:moonrise', - 'carbon:moonset', - 'carbon:mostly-cloudy', - 'carbon:mostly-cloudy-night', - 'carbon:mountain', - 'carbon:mov', - 'carbon:move', - 'carbon:movement', - 'carbon:mp3', - 'carbon:mp4', - 'carbon:mpeg', - 'carbon:mpg2', - 'carbon:music', - 'carbon:music-add', - 'carbon:music-remove', - 'carbon:name-space', - 'carbon:navaid-civil', - 'carbon:navaid-dme', - 'carbon:navaid-helipad', - 'carbon:navaid-military', - 'carbon:navaid-military-civil', - 'carbon:navaid-ndb', - 'carbon:navaid-ndb-dme', - 'carbon:navaid-private', - 'carbon:navaid-seaplane', - 'carbon:navaid-tacan', - 'carbon:navaid-vhfor', - 'carbon:navaid-vor', - 'carbon:navaid-vordme', - 'carbon:navaid-vortac', - 'carbon:need', - 'carbon:network-1', - 'carbon:network-2', - 'carbon:network-3', - 'carbon:network-3-reference', - 'carbon:network-4', - 'carbon:network-4-reference', - 'carbon:network-admin-control', - 'carbon:network-enterprise', - 'carbon:network-overlay', - 'carbon:network-public', - 'carbon:new-tab', - 'carbon:next-filled', - 'carbon:next-outline', - 'carbon:no-image', - 'carbon:no-ticket', - 'carbon:nominal', - 'carbon:nominate', - 'carbon:non-certified', - 'carbon:noodle-bowl', - 'carbon:not-available', - 'carbon:not-sent', - 'carbon:not-sent-filled', - 'carbon:notebook', - 'carbon:notebook-reference', - 'carbon:notification', - 'carbon:notification-filled', - 'carbon:notification-new', - 'carbon:notification-off', - 'carbon:notification-off-filled', - 'carbon:number-0', - 'carbon:number-1', - 'carbon:number-2', - 'carbon:number-3', - 'carbon:number-4', - 'carbon:number-5', - 'carbon:number-6', - 'carbon:number-7', - 'carbon:number-8', - 'carbon:number-9', - 'carbon:number-small-0', - 'carbon:number-small-1', - 'carbon:number-small-2', - 'carbon:number-small-3', - 'carbon:number-small-4', - 'carbon:number-small-5', - 'carbon:number-small-6', - 'carbon:number-small-7', - 'carbon:number-small-8', - 'carbon:number-small-9', - 'carbon:object-storage', - 'carbon:object-storage-alt', - 'carbon:observed-hail', - 'carbon:observed-lightning', - 'carbon:omega', - 'carbon:opacity', - 'carbon:open-panel-bottom', - 'carbon:open-panel-filled-bottom', - 'carbon:open-panel-filled-left', - 'carbon:open-panel-filled-right', - 'carbon:open-panel-filled-top', - 'carbon:open-panel-left', - 'carbon:open-panel-right', - 'carbon:open-panel-top', - 'carbon:operation', - 'carbon:operation-gauge', - 'carbon:operation-if', - 'carbon:operations-field', - 'carbon:operations-record', - 'carbon:order-details', - 'carbon:ordinal', - 'carbon:outage', - 'carbon:outlook-severe', - 'carbon:overflow-menu-horizontal', - 'carbon:overflow-menu-vertical', - 'carbon:overlay', - 'carbon:package', - 'carbon:package-text-analysis', - 'carbon:page-break', - 'carbon:page-first', - 'carbon:page-last', - 'carbon:page-number', - 'carbon:page-scroll', - 'carbon:paint-brush', - 'carbon:paint-brush-alt', - 'carbon:palm-tree', - 'carbon:pan-horizontal', - 'carbon:pan-vertical', - 'carbon:panel-expansion', - 'carbon:paragraph', - 'carbon:parameter', - 'carbon:parent-child', - 'carbon:partition-auto', - 'carbon:partition-collection', - 'carbon:partition-repartition', - 'carbon:partition-same', - 'carbon:partition-specific', - 'carbon:partly-cloudy', - 'carbon:partly-cloudy-night', - 'carbon:partnership', - 'carbon:passenger-drinks', - 'carbon:passenger-plus', - 'carbon:password', - 'carbon:paste', - 'carbon:pause', - 'carbon:pause-filled', - 'carbon:pause-future', - 'carbon:pause-outline', - 'carbon:pause-outline-filled', - 'carbon:pause-past', - 'carbon:pcn-e-node', - 'carbon:pcn-military', - 'carbon:pcn-p-node', - 'carbon:pcn-z-node', - 'carbon:pdf', - 'carbon:pdf-reference', - 'carbon:pedestrian', - 'carbon:pedestrian-child', - 'carbon:pedestrian-family', - 'carbon:pen', - 'carbon:pen-fountain', - 'carbon:pending', - 'carbon:pending-filled', - 'carbon:percentage', - 'carbon:percentage-filled', - 'carbon:person', - 'carbon:person-favorite', - 'carbon:pest', - 'carbon:pet-image-b', - 'carbon:pet-image-o', - 'carbon:phone', - 'carbon:phone-application', - 'carbon:phone-block', - 'carbon:phone-block-filled', - 'carbon:phone-filled', - 'carbon:phone-incoming', - 'carbon:phone-incoming-filled', - 'carbon:phone-ip', - 'carbon:phone-off', - 'carbon:phone-off-filled', - 'carbon:phone-outgoing', - 'carbon:phone-outgoing-filled', - 'carbon:phone-settings', - 'carbon:phone-voice', - 'carbon:phone-voice-filled', - 'carbon:phrase-sentiment', - 'carbon:picnic-area', - 'carbon:piggy-bank', - 'carbon:piggy-bank-slot', - 'carbon:pills', - 'carbon:pills-add', - 'carbon:pills-subtract', - 'carbon:pin', - 'carbon:pin-filled', - 'carbon:plan', - 'carbon:plane', - 'carbon:plane-private', - 'carbon:plane-sea', - 'carbon:play', - 'carbon:play-filled', - 'carbon:play-filled-alt', - 'carbon:play-outline', - 'carbon:play-outline-filled', - 'carbon:playlist', - 'carbon:plug', - 'carbon:plug-filled', - 'carbon:png', - 'carbon:point-of-presence', - 'carbon:pointer-text', - 'carbon:police', - 'carbon:policy', - 'carbon:popup', - 'carbon:port-input', - 'carbon:port-output', - 'carbon:portfolio', - 'carbon:power', - 'carbon:ppt', - 'carbon:presentation-file', - 'carbon:pressure', - 'carbon:pressure-filled', - 'carbon:previous-filled', - 'carbon:previous-outline', - 'carbon:printer', - 'carbon:product', - 'carbon:progress-bar', - 'carbon:progress-bar-round', - 'carbon:promote', - 'carbon:property-relationship', - 'carbon:purchase', - 'carbon:qc-launch', - 'carbon:qq-plot', - 'carbon:qr-code', - 'carbon:quadrant-plot', - 'carbon:query', - 'carbon:query-queue', - 'carbon:queued', - 'carbon:quotes', - 'carbon:radar', - 'carbon:radar-enhanced', - 'carbon:radar-weather', - 'carbon:radio', - 'carbon:radio-button', - 'carbon:radio-button-checked', - 'carbon:radio-combat', - 'carbon:radio-push-to-talk', - 'carbon:rain', - 'carbon:rain-drizzle', - 'carbon:rain-drop', - 'carbon:rain-heavy', - 'carbon:rain-scattered', - 'carbon:rain-scattered-night', - 'carbon:raw', - 'carbon:receipt', - 'carbon:recently-viewed', - 'carbon:recommend', - 'carbon:recording', - 'carbon:recording-filled', - 'carbon:recording-filled-alt', - 'carbon:recycle', - 'carbon:redo', - 'carbon:ref-evapotranspiration', - 'carbon:reference-architecture', - 'carbon:reflect-horizontal', - 'carbon:reflect-vertical', - 'carbon:region-analysis-area', - 'carbon:region-analysis-volume', - 'carbon:registration', - 'carbon:reminder', - 'carbon:reminder-medical', - 'carbon:renew', - 'carbon:repeat', - 'carbon:repeat-one', - 'carbon:replicate', - 'carbon:reply', - 'carbon:reply-all', - 'carbon:repo-artifact', - 'carbon:repo-source-code', - 'carbon:report', - 'carbon:report-data', - 'carbon:request-quote', - 'carbon:research-bloch-sphere', - 'carbon:research-hinton-plot', - 'carbon:research-matrix', - 'carbon:reset', - 'carbon:reset-alt', - 'carbon:restart', - 'carbon:restaurant', - 'carbon:restaurant-fine', - 'carbon:result', - 'carbon:result-draft', - 'carbon:result-new', - 'carbon:result-old', - 'carbon:retry-failed', - 'carbon:review', - 'carbon:rewind-10', - 'carbon:rewind-30', - 'carbon:rewind-5', - 'carbon:road', - 'carbon:road-weather', - 'carbon:roadmap', - 'carbon:rocket', - 'carbon:rotate', - 'carbon:rotate-180', - 'carbon:rotate-360', - 'carbon:rotate-clockwise', - 'carbon:rotate-clockwise-alt', - 'carbon:rotate-clockwise-alt-filled', - 'carbon:rotate-clockwise-filled', - 'carbon:rotate-counterclockwise', - 'carbon:rotate-counterclockwise-alt', - 'carbon:rotate-counterclockwise-alt-filled', - 'carbon:rotate-counterclockwise-filled', - 'carbon:router', - 'carbon:router-voice', - 'carbon:router-wifi', - 'carbon:row', - 'carbon:row-collapse', - 'carbon:row-delete', - 'carbon:row-expand', - 'carbon:row-insert', - 'carbon:rss', - 'carbon:rule', - 'carbon:rule-cancelled', - 'carbon:rule-data-quality', - 'carbon:rule-draft', - 'carbon:rule-filled', - 'carbon:rule-locked', - 'carbon:rule-partial', - 'carbon:rule-test', - 'carbon:ruler', - 'carbon:ruler-alt', - 'carbon:run', - 'carbon:run-mirror', - 'carbon:s', - 'carbon:s-alt', - 'carbon:sailboat-coastal', - 'carbon:sailboat-offshore', - 'carbon:sankey-diagram', - 'carbon:sankey-diagram-alt', - 'carbon:satellite', - 'carbon:satellite-radar', - 'carbon:satellite-weather', - 'carbon:save', - 'carbon:save-annotation', - 'carbon:save-image', - 'carbon:save-model', - 'carbon:save-series', - 'carbon:scale', - 'carbon:scales', - 'carbon:scales-tipped', - 'carbon:scalpel', - 'carbon:scalpel-cursor', - 'carbon:scalpel-lasso', - 'carbon:scalpel-select', - 'carbon:scan', - 'carbon:scan-alt', - 'carbon:scan-disabled', - 'carbon:scatter-matrix', - 'carbon:schematics', - 'carbon:scis-control-tower', - 'carbon:scis-transparent-supply', - 'carbon:scooter', - 'carbon:scooter-front', - 'carbon:screen', - 'carbon:screen-map', - 'carbon:screen-map-set', - 'carbon:screen-off', - 'carbon:script', - 'carbon:script-reference', - 'carbon:sdk', - 'carbon:search', - 'carbon:search-advanced', - 'carbon:search-locate', - 'carbon:search-locate-mirror', - 'carbon:security', - 'carbon:security-services', - 'carbon:select-01', - 'carbon:select-02', - 'carbon:select-window', - 'carbon:send', - 'carbon:send-alt', - 'carbon:send-alt-filled', - 'carbon:send-backward', - 'carbon:send-filled', - 'carbon:send-to-back', - 'carbon:server-dns', - 'carbon:server-proxy', - 'carbon:server-time', - 'carbon:service-desk', - 'carbon:service-id', - 'carbon:session-border-control', - 'carbon:settings', - 'carbon:settings-adjust', - 'carbon:settings-check', - 'carbon:settings-services', - 'carbon:settings-view', - 'carbon:shape-except', - 'carbon:shape-exclude', - 'carbon:shape-intersect', - 'carbon:shape-join', - 'carbon:shape-unite', - 'carbon:share', - 'carbon:share-knowledge', - 'carbon:shopping-bag', - 'carbon:shopping-cart', - 'carbon:shopping-cart-arrow-down', - 'carbon:shopping-cart-arrow-up', - 'carbon:shopping-cart-clear', - 'carbon:shopping-cart-error', - 'carbon:shopping-cart-minus', - 'carbon:shopping-cart-plus', - 'carbon:shopping-catalog', - 'carbon:show-data-cards', - 'carbon:shrink-screen', - 'carbon:shrink-screen-filled', - 'carbon:shuffle', - 'carbon:shuttle', - 'carbon:side-panel-close', - 'carbon:side-panel-close-filled', - 'carbon:side-panel-open', - 'carbon:side-panel-open-filled', - 'carbon:sight', - 'carbon:sigma', - 'carbon:signal-strength', - 'carbon:sim-card', - 'carbon:skill-level', - 'carbon:skill-level-advanced', - 'carbon:skill-level-basic', - 'carbon:skill-level-intermediate', - 'carbon:skip-back', - 'carbon:skip-back-filled', - 'carbon:skip-back-outline', - 'carbon:skip-back-outline-filled', - 'carbon:skip-back-outline-solid', - 'carbon:skip-back-solid-filled', - 'carbon:skip-forward', - 'carbon:skip-forward-filled', - 'carbon:skip-forward-outline', - 'carbon:skip-forward-outline-filled', - 'carbon:skip-forward-outline-solid', - 'carbon:skip-forward-solid-filled', - 'carbon:sleet', - 'carbon:slisor', - 'carbon:slm', - 'carbon:smell', - 'carbon:smoke', - 'carbon:smoothing', - 'carbon:smoothing-cursor', - 'carbon:snooze', - 'carbon:snow', - 'carbon:snow-blizzard', - 'carbon:snow-density', - 'carbon:snow-heavy', - 'carbon:snow-scattered', - 'carbon:snow-scattered-night', - 'carbon:snowflake', - 'carbon:soccer', - 'carbon:software-resource', - 'carbon:software-resource-cluster', - 'carbon:software-resource-resource', - 'carbon:soil-moisture', - 'carbon:soil-moisture-field', - 'carbon:soil-moisture-global', - 'carbon:soil-temperature', - 'carbon:soil-temperature-field', - 'carbon:soil-temperature-global', - 'carbon:solar-panel', - 'carbon:sort-ascending', - 'carbon:sort-descending', - 'carbon:sort-remove', - 'carbon:spell-check', - 'carbon:spine-label', - 'carbon:split', - 'carbon:split-discard', - 'carbon:split-screen', - 'carbon:spray-paint', - 'carbon:sprout', - 'carbon:sql', - 'carbon:stack-limitation', - 'carbon:stacked-move', - 'carbon:stacked-scrolling-1', - 'carbon:stacked-scrolling-2', - 'carbon:stamp', - 'carbon:star', - 'carbon:star-filled', - 'carbon:star-half', - 'carbon:star-review', - 'carbon:status-acknowledge', - 'carbon:status-change', - 'carbon:status-partial-fail', - 'carbon:status-resolved', - 'carbon:stay-inside', - 'carbon:stem-leaf-plot', - 'carbon:stethoscope', - 'carbon:stop', - 'carbon:stop-filled', - 'carbon:stop-filled-alt', - 'carbon:stop-outline', - 'carbon:stop-outline-filled', - 'carbon:stop-sign', - 'carbon:stop-sign-filled', - 'carbon:storage-pool', - 'carbon:storage-request', - 'carbon:store', - 'carbon:storm-tracker', - 'carbon:strawberry', - 'carbon:stress-breath-editor', - 'carbon:string-integer', - 'carbon:string-text', - 'carbon:study-next', - 'carbon:study-previous', - 'carbon:study-read', - 'carbon:study-skip', - 'carbon:study-transfer', - 'carbon:study-unread', - 'carbon:study-view', - 'carbon:sub-volume', - 'carbon:subflow', - 'carbon:subflow-local', - 'carbon:subnet-acl-rules', - 'carbon:subtract', - 'carbon:subtract-alt', - 'carbon:summary-kpi', - 'carbon:summary-kpi-mirror', - 'carbon:sun', - 'carbon:sunrise', - 'carbon:sunset', - 'carbon:support-vector-machine', - 'carbon:svg', - 'carbon:swim', - 'carbon:switch-layer-2', - 'carbon:switch-layer-3', - 'carbon:switcher', - 'carbon:sys-provision', - 'carbon:t', - 'carbon:t-alt', - 'carbon:table', - 'carbon:table-alias', - 'carbon:table-built', - 'carbon:table-of-contents', - 'carbon:table-shortcut', - 'carbon:table-split', - 'carbon:tablet', - 'carbon:tablet-landscape', - 'carbon:tag', - 'carbon:tag-edit', - 'carbon:tag-export', - 'carbon:tag-group', - 'carbon:tag-import', - 'carbon:tag-none', - 'carbon:tank', - 'carbon:task', - 'carbon:task-add', - 'carbon:task-approved', - 'carbon:task-asset-view', - 'carbon:task-complete', - 'carbon:task-location', - 'carbon:task-remove', - 'carbon:task-settings', - 'carbon:task-star', - 'carbon:task-tools', - 'carbon:task-view', - 'carbon:taste', - 'carbon:taxi', - 'carbon:tcp-ip-service', - 'carbon:temperature', - 'carbon:temperature-celsius', - 'carbon:temperature-celsius-alt', - 'carbon:temperature-fahrenheit', - 'carbon:temperature-fahrenheit-alt', - 'carbon:temperature-feels-like', - 'carbon:temperature-frigid', - 'carbon:temperature-hot', - 'carbon:temperature-inversion', - 'carbon:temperature-max', - 'carbon:temperature-min', - 'carbon:temperature-water', - 'carbon:template', - 'carbon:tennis', - 'carbon:tennis-ball', - 'carbon:term', - 'carbon:terminal', - 'carbon:terminal-3270', - 'carbon:test-tool', - 'carbon:text-align-center', - 'carbon:text-align-justify', - 'carbon:text-align-left', - 'carbon:text-align-mixed', - 'carbon:text-align-right', - 'carbon:text-all-caps', - 'carbon:text-annotation-toggle', - 'carbon:text-bold', - 'carbon:text-clear-format', - 'carbon:text-color', - 'carbon:text-creation', - 'carbon:text-fill', - 'carbon:text-font', - 'carbon:text-footnote', - 'carbon:text-highlight', - 'carbon:text-indent', - 'carbon:text-indent-less', - 'carbon:text-indent-more', - 'carbon:text-italic', - 'carbon:text-kerning', - 'carbon:text-leading', - 'carbon:text-line-spacing', - 'carbon:text-link', - 'carbon:text-link-analysis', - 'carbon:text-mining', - 'carbon:text-mining-applier', - 'carbon:text-new-line', - 'carbon:text-scale', - 'carbon:text-selection', - 'carbon:text-small-caps', - 'carbon:text-strikethrough', - 'carbon:text-subscript', - 'carbon:text-superscript', - 'carbon:text-tracking', - 'carbon:text-underline', - 'carbon:text-vertical-alignment', - 'carbon:text-wrap', - 'carbon:theater', - 'carbon:this-side-up', - 'carbon:threshold', - 'carbon:thumbnail-1', - 'carbon:thumbnail-2', - 'carbon:thumbnail-preview', - 'carbon:thumbs-down', - 'carbon:thumbs-down-filled', - 'carbon:thumbs-up', - 'carbon:thumbs-up-filled', - 'carbon:thunderstorm', - 'carbon:thunderstorm-scattered', - 'carbon:thunderstorm-scattered-night', - 'carbon:thunderstorm-severe', - 'carbon:thunderstorm-strong', - 'carbon:ticket', - 'carbon:tides', - 'carbon:tif', - 'carbon:time', - 'carbon:time-plot', - 'carbon:timer', - 'carbon:tool-box', - 'carbon:tool-kit', - 'carbon:tools', - 'carbon:tools-alt', - 'carbon:tornado', - 'carbon:tornado-warning', - 'carbon:touch-1', - 'carbon:touch-1-down', - 'carbon:touch-1-down-filled', - 'carbon:touch-1-filled', - 'carbon:touch-2', - 'carbon:touch-2-filled', - 'carbon:touch-interaction', - 'carbon:traffic-cone', - 'carbon:traffic-event', - 'carbon:traffic-flow', - 'carbon:traffic-flow-incident', - 'carbon:traffic-incident', - 'carbon:traffic-weather-incident', - 'carbon:train', - 'carbon:train-heart', - 'carbon:train-profile', - 'carbon:train-speed', - 'carbon:train-ticket', - 'carbon:train-time', - 'carbon:tram', - 'carbon:transform-binary', - 'carbon:transform-instructions', - 'carbon:transform-language', - 'carbon:transgender', - 'carbon:translate', - 'carbon:transmission-lte', - 'carbon:transpose', - 'carbon:trash-can', - 'carbon:tree', - 'carbon:tree-fall-risk', - 'carbon:tree-view', - 'carbon:tree-view-alt', - 'carbon:trophy', - 'carbon:trophy-filled', - 'carbon:tropical-storm', - 'carbon:tropical-storm-model-tracks', - 'carbon:tropical-storm-tracks', - 'carbon:tropical-warning', - 'carbon:tsq', - 'carbon:tsunami', - 'carbon:tsv', - 'carbon:two-factor-authentication', - 'carbon:two-person-lift', - 'carbon:txt', - 'carbon:txt-reference', - 'carbon:type-pattern', - 'carbon:types', - 'carbon:u1', - 'carbon:u2', - 'carbon:u3', - 'carbon:umbrella', - 'carbon:undefined', - 'carbon:undefined-filled', - 'carbon:undo', - 'carbon:ungroup-objects', - 'carbon:unknown', - 'carbon:unknown-filled', - 'carbon:unlink', - 'carbon:unlocked', - 'carbon:unsaved', - 'carbon:up-to-top', - 'carbon:update-now', - 'carbon:upgrade', - 'carbon:upload', - 'carbon:usb', - 'carbon:user', - 'carbon:user-access', - 'carbon:user-activity', - 'carbon:user-admin', - 'carbon:user-avatar', - 'carbon:user-avatar-filled', - 'carbon:user-avatar-filled-alt', - 'carbon:user-certification', - 'carbon:user-data', - 'carbon:user-favorite', - 'carbon:user-favorite-alt', - 'carbon:user-favorite-alt-filled', - 'carbon:user-filled', - 'carbon:user-follow', - 'carbon:user-identification', - 'carbon:user-military', - 'carbon:user-multiple', - 'carbon:user-online', - 'carbon:user-profile', - 'carbon:user-profile-alt', - 'carbon:user-role', - 'carbon:user-service-desk', - 'carbon:user-settings', - 'carbon:user-simulation', - 'carbon:user-speaker', - 'carbon:user-sponsor', - 'carbon:user-x-ray', - 'carbon:uv-index', - 'carbon:uv-index-alt', - 'carbon:uv-index-filled', - 'carbon:value-variable', - 'carbon:van', - 'carbon:vegetation-asset', - 'carbon:vehicle-api', - 'carbon:vehicle-connected', - 'carbon:vehicle-insights', - 'carbon:vehicle-services', - 'carbon:version', - 'carbon:version-major', - 'carbon:version-minor', - 'carbon:version-patch', - 'carbon:vertical-view', - 'carbon:video', - 'carbon:video-add', - 'carbon:video-chat', - 'carbon:video-filled', - 'carbon:video-off', - 'carbon:video-off-filled', - 'carbon:view', - 'carbon:view-filled', - 'carbon:view-mode-1', - 'carbon:view-mode-2', - 'carbon:view-next', - 'carbon:view-off', - 'carbon:view-off-filled', - 'carbon:virtual-column', - 'carbon:virtual-column-key', - 'carbon:virtual-desktop', - 'carbon:virtual-machine', - 'carbon:virtual-private-cloud', - 'carbon:virtual-private-cloud-alt', - 'carbon:visual-recognition', - 'carbon:vlan', - 'carbon:vlan-ibm', - 'carbon:vmdk-disk', - 'carbon:voice-activate', - 'carbon:voicemail', - 'carbon:volume-block-storage', - 'carbon:volume-down', - 'carbon:volume-down-alt', - 'carbon:volume-down-filled', - 'carbon:volume-down-filled-alt', - 'carbon:volume-file-storage', - 'carbon:volume-mute', - 'carbon:volume-mute-filled', - 'carbon:volume-object-storage', - 'carbon:volume-up', - 'carbon:volume-up-alt', - 'carbon:volume-up-filled', - 'carbon:volume-up-filled-alt', - 'carbon:vpn', - 'carbon:vpn-connection', - 'carbon:vpn-policy', - 'carbon:wallet', - 'carbon:warning', - 'carbon:warning-alt', - 'carbon:warning-alt-filled', - 'carbon:warning-alt-inverted', - 'carbon:warning-alt-inverted-filled', - 'carbon:warning-filled', - 'carbon:warning-hex', - 'carbon:warning-hex-filled', - 'carbon:warning-other', - 'carbon:warning-square', - 'carbon:warning-square-filled', - 'carbon:watch', - 'carbon:watson', - 'carbon:watson-machine-learning', - 'carbon:wave-direction', - 'carbon:wave-height', - 'carbon:wave-period', - 'carbon:weather-front-cold', - 'carbon:weather-front-stationary', - 'carbon:weather-front-warm', - 'carbon:weather-station', - 'carbon:webhook', - 'carbon:websheet', - 'carbon:wheat', - 'carbon:white-paper', - 'carbon:wifi', - 'carbon:wifi-bridge', - 'carbon:wifi-bridge-alt', - 'carbon:wifi-controller', - 'carbon:wifi-not-secure', - 'carbon:wifi-off', - 'carbon:wifi-secure', - 'carbon:wikis', - 'carbon:wind-gusts', - 'carbon:wind-power', - 'carbon:wind-stream', - 'carbon:window-auto', - 'carbon:window-base', - 'carbon:window-black-saturation', - 'carbon:window-overlay', - 'carbon:window-preset', - 'carbon:windy', - 'carbon:windy-dust', - 'carbon:windy-snow', - 'carbon:windy-strong', - 'carbon:winter-warning', - 'carbon:wintry-mix', - 'carbon:wireless-checkout', - 'carbon:wmv', - 'carbon:word-cloud', - 'carbon:workflow-automation', - 'carbon:workspace', - 'carbon:workspace-import', - 'carbon:worship', - 'carbon:worship-christian', - 'carbon:worship-jewish', - 'carbon:worship-muslim', - 'carbon:x', - 'carbon:x-axis', - 'carbon:xls', - 'carbon:xml', - 'carbon:y', - 'carbon:y-axis', - 'carbon:z', - 'carbon:z-axis', - 'carbon:z-lpar', - 'carbon:z-systems', - 'carbon:zip', - 'carbon:zip-reference', - 'carbon:zoom-area', - 'carbon:zoom-fit', - 'carbon:zoom-in', - 'carbon:zoom-in-area', - 'carbon:zoom-out', - 'carbon:zoom-out-area', - 'carbon:zoom-pan', - 'carbon:zoom-reset', - 'carbon:zos', - 'carbon:zos-sysplex', - 'carbon:app-switcher', - 'carbon:arrows', - 'carbon:back-to-top', - 'carbon:checkbox-undeterminate', - 'carbon:cloud-lightning', - 'carbon:cloud-rain', - 'carbon:cloud-snow', - 'carbon:delete', - 'carbon:letter-aa-large', - 'carbon:sunny', - 'simple-icons:tiktok', - 'ph:map-pin-fill', -] as const +// prettier-ignore +export const iconList = ["carbon:3d-cursor","carbon:3d-cursor-alt","carbon:3d-curve-auto-colon","carbon:3d-curve-auto-vessels","carbon:3d-curve-manual","carbon:3d-ica","carbon:3d-mpr-toggle","carbon:3d-print-mesh","carbon:3d-software","carbon:3rd-party-connected","carbon:4k","carbon:4k-filled","carbon:accessibility","carbon:accessibility-alt","carbon:accessibility-color","carbon:accessibility-color-filled","carbon:account","carbon:accumulation-ice","carbon:accumulation-precipitation","carbon:accumulation-rain","carbon:accumulation-snow","carbon:activity","carbon:add","carbon:add-alt","carbon:add-comment","carbon:add-filled","carbon:agriculture-analytics","carbon:ai-results","carbon:ai-results-high","carbon:ai-results-low","carbon:ai-results-medium","carbon:ai-results-urgent","carbon:ai-results-very-high","carbon:ai-status","carbon:ai-status-complete","carbon:ai-status-failed","carbon:ai-status-in-progress","carbon:ai-status-queued","carbon:ai-status-rejected","carbon:airline-digital-gate","carbon:airline-manage-gates","carbon:airline-passenger-care","carbon:airline-rapid-board","carbon:airplay","carbon:airplay-filled","carbon:airport-01","carbon:airport-02","carbon:airport-location","carbon:alarm","carbon:alarm-add","carbon:alarm-subtract","carbon:align-box-bottom-center","carbon:align-box-bottom-left","carbon:align-box-bottom-right","carbon:align-box-middle-center","carbon:align-box-middle-left","carbon:align-box-middle-right","carbon:align-box-top-center","carbon:align-box-top-left","carbon:align-box-top-right","carbon:align-horizontal-center","carbon:align-horizontal-left","carbon:align-horizontal-right","carbon:align-vertical-bottom","carbon:align-vertical-center","carbon:align-vertical-top","carbon:analytics","carbon:analytics-custom","carbon:analytics-reference","carbon:angle","carbon:annotation-visibility","carbon:aperture","carbon:api","carbon:api-1","carbon:app","carbon:app-connectivity","carbon:apple","carbon:apple-dash","carbon:application","carbon:application-mobile","carbon:application-virtual","carbon:application-web","carbon:apps","carbon:archive","carbon:area","carbon:area-custom","carbon:arrival","carbon:arrow-annotation","carbon:arrow-down","carbon:arrow-down-left","carbon:arrow-down-right","carbon:arrow-left","carbon:arrow-right","carbon:arrow-shift-down","carbon:arrow-up","carbon:arrow-up-left","carbon:arrow-up-right","carbon:arrows-horizontal","carbon:arrows-vertical","carbon:asleep","carbon:asleep-filled","carbon:assembly","carbon:assembly-cluster","carbon:assembly-reference","carbon:asset","carbon:asset-confirm","carbon:asset-digital-twin","carbon:asset-view","carbon:asterisk","carbon:at","carbon:attachment","carbon:audio-console","carbon:augmented-reality","carbon:auto-scroll","carbon:automatic","carbon:autoscaling","carbon:awake","carbon:badge","carbon:baggage-claim","carbon:bar","carbon:barcode","carbon:bare-metal-server","carbon:bare-metal-server-01","carbon:bare-metal-server-02","carbon:barrier","carbon:basketball","carbon:bastion-host","carbon:bat","carbon:batch-job","carbon:batch-job-step","carbon:battery-charging","carbon:battery-empty","carbon:battery-full","carbon:battery-half","carbon:battery-low","carbon:battery-quarter","carbon:bee","carbon:bee-bat","carbon:beta","carbon:bicycle","carbon:binoculars","carbon:bloch-sphere","carbon:block-storage","carbon:block-storage-alt","carbon:blockchain","carbon:blog","carbon:bluetooth","carbon:bluetooth-off","carbon:book","carbon:bookmark","carbon:bookmark-add","carbon:bookmark-filled","carbon:boolean","carbon:boot","carbon:boot-volume","carbon:boot-volume-alt","carbon:border-bottom","carbon:border-full","carbon:border-left","carbon:border-none","carbon:border-right","carbon:border-top","carbon:bot","carbon:bottles-01","carbon:bottles-01-dash","carbon:bottles-02","carbon:bottles-02-dash","carbon:bottles-container","carbon:box","carbon:box-extra-large","carbon:box-large","carbon:box-medium","carbon:box-plot","carbon:box-small","carbon:branch","carbon:breaking-change","carbon:brightness-contrast","carbon:bring-forward","carbon:bring-to-front","carbon:brush-freehand","carbon:brush-polygon","carbon:build-tool","carbon:building","carbon:building-insights-1","carbon:building-insights-2","carbon:building-insights-3","carbon:bullhorn","carbon:buoy","carbon:bus","carbon:button-centered","carbon:button-flush-left","carbon:cabin-care","carbon:cabin-care-alert","carbon:cabin-care-alt","carbon:cad","carbon:cafe","carbon:calculation","carbon:calculation-alt","carbon:calculator","carbon:calculator-check","carbon:calendar","carbon:calendar-add","carbon:calendar-add-alt","carbon:calendar-heat-map","carbon:calendar-settings","carbon:calendar-tools","carbon:calibrate","carbon:camera","carbon:camera-action","carbon:campsite","carbon:car","carbon:car-front","carbon:carbon","carbon:carbon-accounting","carbon:carbon-for-ibm-dotcom","carbon:carbon-for-ibm-product","carbon:carbon-ui-builder","carbon:caret-down","carbon:caret-left","carbon:caret-right","carbon:caret-sort","carbon:caret-sort-down","carbon:caret-sort-up","carbon:caret-up","carbon:carousel-horizontal","carbon:carousel-vertical","carbon:catalog","carbon:catalog-publish","carbon:categories","carbon:category","carbon:category-add","carbon:category-and","carbon:category-new","carbon:category-new-each","carbon:ccx","carbon:cd-archive","carbon:cd-create-archive","carbon:cd-create-exchange","carbon:cda","carbon:cell-tower","carbon:center-circle","carbon:center-square","carbon:center-to-fit","carbon:certificate","carbon:certificate-check","carbon:change-catalog","carbon:character-decimal","carbon:character-fraction","carbon:character-integer","carbon:character-lower-case","carbon:character-negative-number","carbon:character-patterns","carbon:character-sentence-case","carbon:character-upper-case","carbon:character-whole-number","carbon:charging-station","carbon:charging-station-filled","carbon:chart-3d","carbon:chart-area","carbon:chart-area-smooth","carbon:chart-area-stepper","carbon:chart-average","carbon:chart-bar","carbon:chart-bar-floating","carbon:chart-bar-overlay","carbon:chart-bar-stacked","carbon:chart-bar-target","carbon:chart-bubble","carbon:chart-bubble-packed","carbon:chart-bullet","carbon:chart-candlestick","carbon:chart-cluster-bar","carbon:chart-column","carbon:chart-column-floating","carbon:chart-column-target","carbon:chart-combo","carbon:chart-combo-stacked","carbon:chart-custom","carbon:chart-error-bar","carbon:chart-error-bar-alt","carbon:chart-evaluation","carbon:chart-high-low","carbon:chart-histogram","carbon:chart-line","carbon:chart-line-data","carbon:chart-line-smooth","carbon:chart-logistic-regression","carbon:chart-marimekko","carbon:chart-maximum","carbon:chart-median","carbon:chart-minimum","carbon:chart-multi-line","carbon:chart-multitype","carbon:chart-network","carbon:chart-parallel","carbon:chart-pie","carbon:chart-point","carbon:chart-population","carbon:chart-radar","carbon:chart-radial","carbon:chart-relationship","carbon:chart-ring","carbon:chart-river","carbon:chart-rose","carbon:chart-scatter","carbon:chart-spiral","carbon:chart-stacked","carbon:chart-stepper","carbon:chart-sunburst","carbon:chart-t-sne","carbon:chart-treemap","carbon:chart-venn-diagram","carbon:chart-violin-plot","carbon:chart-waterfall","carbon:chart-win-loss","carbon:chat","carbon:chat-bot","carbon:chat-launch","carbon:chat-off","carbon:chat-operational","carbon:checkbox","carbon:checkbox-checked","carbon:checkbox-checked-filled","carbon:checkbox-indeterminate","carbon:checkbox-indeterminate-filled","carbon:checkbox-undeterminate-filled","carbon:checkmark","carbon:checkmark-filled","carbon:checkmark-filled-error","carbon:checkmark-filled-warning","carbon:checkmark-outline","carbon:checkmark-outline-error","carbon:checkmark-outline-warning","carbon:chemistry","carbon:chemistry-reference","carbon:chevron-down","carbon:chevron-left","carbon:chevron-mini","carbon:chevron-right","carbon:chevron-sort","carbon:chevron-sort-down","carbon:chevron-sort-up","carbon:chevron-up","carbon:chip","carbon:choices","carbon:choose-item","carbon:choropleth-map","carbon:cics-cmas","carbon:cics-db2-connection","carbon:cics-explorer","carbon:cics-program","carbon:cics-region","carbon:cics-region-alt","carbon:cics-region-routing","carbon:cics-region-target","carbon:cics-sit","carbon:cics-sit-overrides","carbon:cics-system-group","carbon:cics-transaction-server-zos","carbon:cics-wui-region","carbon:cicsplex","carbon:circle-dash","carbon:circle-filled","carbon:circle-measurement","carbon:circle-packing","carbon:circle-solid","carbon:circuit-composer","carbon:classification","carbon:classifier-language","carbon:clean","carbon:close","carbon:close-filled","carbon:close-outline","carbon:closed-caption","carbon:closed-caption-alt","carbon:closed-caption-filled","carbon:cloud","carbon:cloud-alerting","carbon:cloud-app","carbon:cloud-auditing","carbon:cloud-ceiling","carbon:cloud-data-ops","carbon:cloud-download","carbon:cloud-foundry-1","carbon:cloud-foundry-2","carbon:cloud-logging","carbon:cloud-monitoring","carbon:cloud-offline","carbon:cloud-registry","carbon:cloud-satellite","carbon:cloud-satellite-config","carbon:cloud-satellite-link","carbon:cloud-satellite-services","carbon:cloud-service-management","carbon:cloud-services","carbon:cloud-upload","carbon:cloudy","carbon:cobb-angle","carbon:code","carbon:code-hide","carbon:code-reference","carbon:code-signing-service","carbon:cognitive","carbon:collaborate","carbon:collapse-all","carbon:collapse-categories","carbon:color-palette","carbon:color-switch","carbon:column","carbon:column-delete","carbon:column-dependency","carbon:column-insert","carbon:commit","carbon:communication-unified","carbon:compare","carbon:compass","carbon:composer-edit","carbon:concept","carbon:condition-point","carbon:condition-wait-point","carbon:connect","carbon:connect-recursive","carbon:connect-reference","carbon:connect-source","carbon:connect-target","carbon:connection-receive","carbon:connection-send","carbon:connection-signal","carbon:connection-signal-off","carbon:connection-two-way","carbon:construction","carbon:container-registry","carbon:container-services","carbon:container-software","carbon:content-delivery-network","carbon:content-view","carbon:continue","carbon:continue-filled","carbon:continuous-deployment","carbon:continuous-integration","carbon:contour-draw","carbon:contour-edit","carbon:contour-finding","carbon:contrast","carbon:convert-to-cloud","carbon:cookie","carbon:copy","carbon:copy-file","carbon:copy-link","carbon:corn","carbon:corner","carbon:coronavirus","carbon:cost","carbon:cost-total","carbon:cough","carbon:course","carbon:covariate","carbon:credentials","carbon:crop","carbon:crop-growth","carbon:crop-health","carbon:cross-reference","carbon:cross-tab","carbon:crossroads","carbon:crowd-report","carbon:crowd-report-filled","carbon:csv","carbon:cu1","carbon:cu3","carbon:cube","carbon:cube-view","carbon:currency","carbon:currency-baht","carbon:currency-dollar","carbon:currency-euro","carbon:currency-lira","carbon:currency-pound","carbon:currency-ruble","carbon:currency-rupee","carbon:currency-shekel","carbon:currency-won","carbon:currency-yen","carbon:cursor-1","carbon:cursor-2","carbon:customer-service","carbon:cut","carbon:cut-in-half","carbon:cut-out","carbon:cy","carbon:cyclist","carbon:cz","carbon:dashboard","carbon:dashboard-reference","carbon:data-1","carbon:data-2","carbon:data-accessor","carbon:data-backup","carbon:data-base","carbon:data-base-alt","carbon:data-bin","carbon:data-blob","carbon:data-categorical","carbon:data-center","carbon:data-check","carbon:data-class","carbon:data-collection","carbon:data-connected","carbon:data-definition","carbon:data-diode","carbon:data-enrichment","carbon:data-enrichment-add","carbon:data-error","carbon:data-format","carbon:data-player","carbon:data-quality-definition","carbon:data-reference","carbon:data-refinery","carbon:data-refinery-reference","carbon:data-regular","carbon:data-set","carbon:data-share","carbon:data-structured","carbon:data-table","carbon:data-table-reference","carbon:data-unstructured","carbon:data-view","carbon:data-view-alt","carbon:data-vis-1","carbon:data-vis-2","carbon:data-vis-3","carbon:data-vis-4","carbon:data-volume","carbon:data-volume-alt","carbon:database-datastax","carbon:database-elastic","carbon:database-enterprise-db2","carbon:database-etcd","carbon:database-mongodb","carbon:database-postgresql","carbon:database-rabbit","carbon:database-redis","carbon:datastore","carbon:db2-buffer-pool","carbon:db2-data-sharing-group","carbon:db2-database","carbon:debug","carbon:decision-tree","carbon:delivery","carbon:delivery-add","carbon:delivery-parcel","carbon:delivery-truck","carbon:denominate","carbon:departure","carbon:deploy","carbon:deploy-rules","carbon:deployment-pattern","carbon:deployment-policy","carbon:deployment-unit-data","carbon:deployment-unit-execution","carbon:deployment-unit-installation","carbon:deployment-unit-presentation","carbon:deployment-unit-technical-data","carbon:deployment-unit-technical-execution","carbon:deployment-unit-technical-installation","carbon:deployment-unit-technical-presentation","carbon:desk-adjustable","carbon:development","carbon:devices","carbon:dew-point","carbon:dew-point-filled","carbon:diagram","carbon:diagram-reference","carbon:dicom-6000","carbon:dicom-overlay","carbon:direct-link","carbon:direction-bear-right-01","carbon:direction-bear-right-01-filled","carbon:direction-bear-right-02","carbon:direction-bear-right-02-filled","carbon:direction-curve","carbon:direction-curve-filled","carbon:direction-fork","carbon:direction-fork-filled","carbon:direction-loop-left","carbon:direction-loop-left-filled","carbon:direction-loop-right","carbon:direction-loop-right-filled","carbon:direction-merge","carbon:direction-merge-filled","carbon:direction-right-01","carbon:direction-right-01-filled","carbon:direction-right-02","carbon:direction-right-02-filled","carbon:direction-rotary-first-right","carbon:direction-rotary-first-right-filled","carbon:direction-rotary-right","carbon:direction-rotary-right-filled","carbon:direction-rotary-straight","carbon:direction-rotary-straight-filled","carbon:direction-sharp-turn","carbon:direction-sharp-turn-filled","carbon:direction-straight","carbon:direction-straight-filled","carbon:direction-straight-right","carbon:direction-straight-right-filled","carbon:direction-u-turn","carbon:direction-u-turn-filled","carbon:directory-domain","carbon:distribute-horizontal-center","carbon:distribute-horizontal-left","carbon:distribute-horizontal-right","carbon:distribute-vertical-bottom","carbon:distribute-vertical-center","carbon:distribute-vertical-top","carbon:dna","carbon:dns-services","carbon:doc","carbon:document","carbon:document-add","carbon:document-attachment","carbon:document-audio","carbon:document-blank","carbon:document-download","carbon:document-epdf","carbon:document-export","carbon:document-horizontal","carbon:document-import","carbon:document-multiple-01","carbon:document-multiple-02","carbon:document-pdf","carbon:document-preliminary","carbon:document-protected","carbon:document-security","carbon:document-sentiment","carbon:document-signed","carbon:document-sketch","carbon:document-subtract","carbon:document-tasks","carbon:document-unknown","carbon:document-unprotected","carbon:document-vertical","carbon:document-video","carbon:document-view","carbon:document-word-processor","carbon:document-word-processor-reference","carbon:dog-walker","carbon:dot-mark","carbon:double-integer","carbon:down-to-bottom","carbon:download","carbon:download-study","carbon:drag-horizontal","carbon:drag-vertical","carbon:draggable","carbon:draw","carbon:drill-back","carbon:drill-down","carbon:drill-through","carbon:drink-01","carbon:drink-02","carbon:driver-analysis","carbon:drone","carbon:drone-delivery","carbon:drone-front","carbon:drone-video","carbon:drop-photo","carbon:drop-photo-filled","carbon:drought","carbon:dvr","carbon:earth","carbon:earth-americas","carbon:earth-americas-filled","carbon:earth-europe-africa","carbon:earth-europe-africa-filled","carbon:earth-filled","carbon:earth-southeast-asia","carbon:earth-southeast-asia-filled","carbon:earthquake","carbon:edge-cluster","carbon:edge-device","carbon:edge-enhancement","carbon:edge-enhancement-01","carbon:edge-enhancement-02","carbon:edge-enhancement-03","carbon:edge-node","carbon:edge-node-alt","carbon:edge-service","carbon:edit","carbon:edit-filter","carbon:edit-off","carbon:edt-loop","carbon:education","carbon:email","carbon:email-new","carbon:encryption","carbon:energy-renewable","carbon:enterprise","carbon:equalizer","carbon:erase","carbon:erase-3d","carbon:error","carbon:error-filled","carbon:error-outline","carbon:event","carbon:event-schedule","carbon:events","carbon:events-alt","carbon:exam-mode","carbon:executable-program","carbon:exit","carbon:expand-all","carbon:expand-categories","carbon:explore","carbon:export","carbon:eyedropper","carbon:face-activated","carbon:face-activated-add","carbon:face-activated-filled","carbon:face-add","carbon:face-cool","carbon:face-dissatisfied","carbon:face-dissatisfied-filled","carbon:face-dizzy","carbon:face-dizzy-filled","carbon:face-mask","carbon:face-neutral","carbon:face-neutral-filled","carbon:face-pending","carbon:face-pending-filled","carbon:face-satisfied","carbon:face-satisfied-filled","carbon:face-wink","carbon:face-wink-filled","carbon:factor","carbon:fade","carbon:favorite","carbon:favorite-filled","carbon:favorite-half","carbon:fetch-upload","carbon:fetch-upload-cloud","carbon:file-storage","carbon:filter","carbon:filter-edit","carbon:filter-remove","carbon:filter-reset","carbon:finance","carbon:fingerprint-recognition","carbon:fire","carbon:firewall","carbon:firewall-classic","carbon:fish","carbon:fish-multiple","carbon:fit-to-height","carbon:fit-to-screen","carbon:fit-to-width","carbon:flag","carbon:flag-filled","carbon:flagging-taxi","carbon:flash","carbon:flash-filled","carbon:flash-off","carbon:flash-off-filled","carbon:flight-international","carbon:flight-roster","carbon:flight-schedule","carbon:floating-ip","carbon:flood","carbon:flood-warning","carbon:floorplan","carbon:flow","carbon:flow-connection","carbon:flow-data","carbon:flow-logs-vpc","carbon:flow-modeler","carbon:flow-modeler-reference","carbon:flow-stream","carbon:flow-stream-reference","carbon:fog","carbon:folder","carbon:folder-add","carbon:folder-details","carbon:folder-details-reference","carbon:folder-move-to","carbon:folder-off","carbon:folder-open","carbon:folder-parent","carbon:folder-shared","carbon:folders","carbon:forecast-hail","carbon:forecast-hail-30","carbon:forecast-lightning","carbon:forecast-lightning-30","carbon:fork","carbon:forum","carbon:forward-10","carbon:forward-30","carbon:forward-5","carbon:fragile","carbon:friendship","carbon:fruit-bowl","carbon:function","carbon:function-math","carbon:fusion-blender","carbon:game-console","carbon:game-wireless","carbon:gamification","carbon:gas-station","carbon:gas-station-filled","carbon:gateway","carbon:gateway-api","carbon:gateway-mail","carbon:gateway-public","carbon:gateway-security","carbon:gateway-user-access","carbon:gateway-vpn","carbon:gender-female","carbon:gender-male","carbon:generate-pdf","carbon:gif","carbon:gift","carbon:globe","carbon:gradient","carbon:graphical-data-flow","carbon:grid","carbon:group","carbon:group-access","carbon:group-account","carbon:group-objects","carbon:group-objects-new","carbon:group-objects-save","carbon:group-presentation","carbon:group-resource","carbon:group-security","carbon:growth","carbon:gui","carbon:gui-management","carbon:h","carbon:hail","carbon:hanging-protocol","carbon:harbor","carbon:hardware-security-module","carbon:hashtag","carbon:haze","carbon:haze-night","carbon:hd","carbon:hd-filled","carbon:hdr","carbon:headphones","carbon:headset","carbon:health-cross","carbon:hearing","carbon:heat-map","carbon:heat-map-02","carbon:heat-map-03","carbon:heat-map-stocks","carbon:helicopter","carbon:help","carbon:help-desk","carbon:help-filled","carbon:hinton-plot","carbon:hl7-attributes","carbon:hole-filling","carbon:hole-filling-cursor","carbon:home","carbon:horizontal-view","carbon:hospital","carbon:hospital-bed","carbon:hotel","carbon:hourglass","carbon:html","carbon:html-reference","carbon:http","carbon:humidity","carbon:humidity-alt","carbon:hurricane","carbon:hybrid-networking","carbon:hybrid-networking-alt","carbon:ibm-bluepay","carbon:ibm-cloud","carbon:ibm-cloud-app-id","carbon:ibm-cloud-citrix-daas","carbon:ibm-cloud-continuous-delivery","carbon:ibm-cloud-dedicated-host","carbon:ibm-cloud-direct-link-1-connect","carbon:ibm-cloud-direct-link-1-dedicated","carbon:ibm-cloud-direct-link-1-dedicated-hosting","carbon:ibm-cloud-direct-link-1-exchange","carbon:ibm-cloud-direct-link-2-connect","carbon:ibm-cloud-direct-link-2-dedicated","carbon:ibm-cloud-direct-link-2-dedicated-hosting","carbon:ibm-cloud-event-notification","carbon:ibm-cloud-event-streams","carbon:ibm-cloud-for-education","carbon:ibm-cloud-hsm","carbon:ibm-cloud-hyper-protect-crypto-services","carbon:ibm-cloud-hyper-protect-dbaas","carbon:ibm-cloud-hyper-protect-vs","carbon:ibm-cloud-internet-services","carbon:ibm-cloud-ipsec-vpn","carbon:ibm-cloud-key-protect","carbon:ibm-cloud-kubernetes-service","carbon:ibm-cloud-logging","carbon:ibm-cloud-mass-data-migration","carbon:ibm-cloud-pak-applications","carbon:ibm-cloud-pak-business-automation","carbon:ibm-cloud-pak-data","carbon:ibm-cloud-pak-integration","carbon:ibm-cloud-pak-manta-automated-data-lineage","carbon:ibm-cloud-pak-multicloud-mgmt","carbon:ibm-cloud-pak-netezza","carbon:ibm-cloud-pak-network-automation","carbon:ibm-cloud-pak-security","carbon:ibm-cloud-pak-system","carbon:ibm-cloud-pak-watson-aiops","carbon:ibm-cloud-pal","carbon:ibm-cloud-privileged-access-gateway","carbon:ibm-cloud-projects","carbon:ibm-cloud-resiliency","carbon:ibm-cloud-secrets-manager","carbon:ibm-cloud-security-compliance-center","carbon:ibm-cloud-security-compliance-center-workload-protection","carbon:ibm-cloud-subnets","carbon:ibm-cloud-sysdig-secure","carbon:ibm-cloud-transit-gateway","carbon:ibm-cloud-vpc-endpoints","carbon:ibm-content-services","carbon:ibm-data-replication","carbon:ibm-datastage","carbon:ibm-db2","carbon:ibm-db2-alt","carbon:ibm-match-360","carbon:ibm-mq","carbon:ibm-open-enterprise-languages","carbon:ibm-openshift-container-platform-on-vpc-for-regulated-industries","carbon:ibm-power-vs","carbon:ibm-power-with-vpc","carbon:ibm-private-path-services","carbon:ibm-process-mining","carbon:ibm-sap-on-power","carbon:ibm-secure-infrastructure-on-vpc-for-regulated-industries","carbon:ibm-security","carbon:ibm-security-services","carbon:ibm-telehealth","carbon:ibm-tenet","carbon:ibm-toolchain","carbon:ibm-vsi-on-vpc-for-regulated-industries","carbon:ibm-watson-assistant","carbon:ibm-watson-discovery","carbon:ibm-watson-knowledge-catalog","carbon:ibm-watson-knowledge-studio","carbon:ibm-watson-language-translator","carbon:ibm-watson-machine-learning","carbon:ibm-watson-natural-language-classifier","carbon:ibm-watson-natural-language-understanding","carbon:ibm-watson-openscale","carbon:ibm-watson-orders","carbon:ibm-watson-query","carbon:ibm-watson-speech-to-text","carbon:ibm-watson-studio","carbon:ibm-watson-text-to-speech","carbon:ibm-watson-tone-analyzer","carbon:ibm-z-cloud-mod-stack","carbon:ibm-z-cloud-provisioning","carbon:ibm-z-environments-dev-sec-ops","carbon:ibm-z-os-ai-control-interface","carbon:ibm-z-os-containers","carbon:ibm-z-os-package-manager","carbon:ica-2d","carbon:ice-accretion","carbon:ice-vision","carbon:id","carbon:id-management","carbon:idea","carbon:identification","carbon:image","carbon:image-copy","carbon:image-medical","carbon:image-reference","carbon:image-search","carbon:image-search-alt","carbon:image-service","carbon:import-export","carbon:improve-relevance","carbon:in-progress","carbon:in-progress-error","carbon:in-progress-warning","carbon:incomplete","carbon:incomplete-cancel","carbon:incomplete-error","carbon:incomplete-warning","carbon:increase-level","carbon:industry","carbon:information","carbon:information-disabled","carbon:information-filled","carbon:information-square","carbon:information-square-filled","carbon:infrastructure-classic","carbon:insert","carbon:insert-page","carbon:insert-syntax","carbon:inspection","carbon:instance-bx","carbon:instance-classic","carbon:instance-cx","carbon:instance-mx","carbon:instance-virtual","carbon:integration","carbon:intent-request-active","carbon:intent-request-create","carbon:intent-request-heal","carbon:intent-request-inactive","carbon:intent-request-scale-in","carbon:intent-request-scale-out","carbon:intent-request-uninstall","carbon:intent-request-upgrade","carbon:interactions","carbon:interactive-segmentation-cursor","carbon:intersect","carbon:intrusion-prevention","carbon:inventory-management","carbon:iot-connect","carbon:iot-platform","carbon:iso","carbon:iso-filled","carbon:iso-outline","carbon:join-full","carbon:join-inner","carbon:join-left","carbon:join-outer","carbon:join-right","carbon:jpg","carbon:json","carbon:json-reference","carbon:jump-link","carbon:keep-dry","carbon:keyboard","carbon:keyboard-off","carbon:kubernetes","carbon:label","carbon:language","carbon:laptop","carbon:lasso","carbon:lasso-polygon","carbon:launch","carbon:launch-study-1","carbon:launch-study-2","carbon:launch-study-3","carbon:layers","carbon:legend","carbon:letter-aa","carbon:letter-bb","carbon:letter-cc","carbon:letter-dd","carbon:letter-ee","carbon:letter-ff","carbon:letter-gg","carbon:letter-hh","carbon:letter-ii","carbon:letter-jj","carbon:letter-kk","carbon:letter-ll","carbon:letter-mm","carbon:letter-nn","carbon:letter-oo","carbon:letter-pp","carbon:letter-qq","carbon:letter-rr","carbon:letter-ss","carbon:letter-tt","carbon:letter-uu","carbon:letter-vv","carbon:letter-ww","carbon:letter-xx","carbon:letter-yy","carbon:letter-zz","carbon:license","carbon:license-draft","carbon:license-global","carbon:license-maintenance","carbon:license-maintenance-draft","carbon:license-third-party","carbon:license-third-party-draft","carbon:lifesaver","carbon:light","carbon:light-filled","carbon:lightning","carbon:link","carbon:linux","carbon:linux-alt","carbon:list","carbon:list-boxes","carbon:list-bulleted","carbon:list-checked","carbon:list-checked-mirror","carbon:list-dropdown","carbon:list-numbered","carbon:list-numbered-mirror","carbon:load-balancer-application","carbon:load-balancer-classic","carbon:load-balancer-global","carbon:load-balancer-listener","carbon:load-balancer-local","carbon:load-balancer-network","carbon:load-balancer-pool","carbon:load-balancer-vpc","carbon:location","carbon:location-company","carbon:location-company-filled","carbon:location-current","carbon:location-filled","carbon:location-hazard","carbon:location-hazard-filled","carbon:location-heart","carbon:location-heart-filled","carbon:location-person","carbon:location-person-filled","carbon:location-save","carbon:location-star","carbon:location-star-filled","carbon:locked","carbon:logical-partition","carbon:login","carbon:logo-angular","carbon:logo-ansible-community","carbon:logo-delicious","carbon:logo-digg","carbon:logo-discord","carbon:logo-facebook","carbon:logo-figma","carbon:logo-flickr","carbon:logo-github","carbon:logo-glassdoor","carbon:logo-google","carbon:logo-instagram","carbon:logo-invision","carbon:logo-jupyter","carbon:logo-keybase","carbon:logo-kubernetes","carbon:logo-linkedin","carbon:logo-livestream","carbon:logo-mastodon","carbon:logo-medium","carbon:logo-npm","carbon:logo-openshift","carbon:logo-pinterest","carbon:logo-python","carbon:logo-quora","carbon:logo-r-script","carbon:logo-react","carbon:logo-red-hat-ansible","carbon:logo-sketch","carbon:logo-skype","carbon:logo-slack","carbon:logo-snapchat","carbon:logo-stumbleupon","carbon:logo-svelte","carbon:logo-tumblr","carbon:logo-twitter","carbon:logo-vmware","carbon:logo-vmware-alt","carbon:logo-vue","carbon:logo-wechat","carbon:logo-xing","carbon:logo-yelp","carbon:logo-youtube","carbon:logout","carbon:loop","carbon:mac-command","carbon:mac-option","carbon:mac-shift","carbon:machine-learning","carbon:machine-learning-model","carbon:magic-wand","carbon:magic-wand-filled","carbon:magnify","carbon:mail-all","carbon:mail-reply","carbon:mammogram","carbon:mammogram-stacked","carbon:manage-protection","carbon:managed-solutions","carbon:map","carbon:map-boundary","carbon:map-boundary-vegetation","carbon:map-center","carbon:map-identify","carbon:marine-warning","carbon:math-curve","carbon:matrix","carbon:maximize","carbon:media-cast","carbon:media-library","carbon:media-library-filled","carbon:medication","carbon:medication-alert","carbon:medication-reminder","carbon:menu","carbon:message-queue","carbon:meter","carbon:meter-alt","carbon:microphone","carbon:microphone-filled","carbon:microphone-off","carbon:microphone-off-filled","carbon:microscope","carbon:microservices-1","carbon:microservices-2","carbon:migrate","carbon:migrate-alt","carbon:milestone","carbon:military-camp","carbon:minimize","carbon:misuse","carbon:misuse-alt","carbon:misuse-outline","carbon:mixed-rain-hail","carbon:mobile","carbon:mobile-add","carbon:mobile-audio","carbon:mobile-check","carbon:mobile-download","carbon:mobile-landscape","carbon:mobility-services","carbon:model","carbon:model-alt","carbon:model-builder","carbon:model-builder-reference","carbon:model-reference","carbon:money","carbon:monster","carbon:monument","carbon:moon","carbon:moonrise","carbon:moonset","carbon:mostly-cloudy","carbon:mostly-cloudy-night","carbon:mountain","carbon:mov","carbon:move","carbon:movement","carbon:mp3","carbon:mp4","carbon:mpeg","carbon:mpg2","carbon:music","carbon:music-add","carbon:music-remove","carbon:name-space","carbon:navaid-civil","carbon:navaid-dme","carbon:navaid-helipad","carbon:navaid-military","carbon:navaid-military-civil","carbon:navaid-ndb","carbon:navaid-ndb-dme","carbon:navaid-private","carbon:navaid-seaplane","carbon:navaid-tacan","carbon:navaid-vhfor","carbon:navaid-vor","carbon:navaid-vordme","carbon:navaid-vortac","carbon:need","carbon:network-1","carbon:network-2","carbon:network-3","carbon:network-3-reference","carbon:network-4","carbon:network-4-reference","carbon:network-admin-control","carbon:network-enterprise","carbon:network-overlay","carbon:network-public","carbon:new-tab","carbon:next-filled","carbon:next-outline","carbon:no-image","carbon:no-ticket","carbon:nominal","carbon:nominate","carbon:non-certified","carbon:noodle-bowl","carbon:not-available","carbon:not-sent","carbon:not-sent-filled","carbon:notebook","carbon:notebook-reference","carbon:notification","carbon:notification-filled","carbon:notification-new","carbon:notification-off","carbon:notification-off-filled","carbon:number-0","carbon:number-1","carbon:number-2","carbon:number-3","carbon:number-4","carbon:number-5","carbon:number-6","carbon:number-7","carbon:number-8","carbon:number-9","carbon:number-small-0","carbon:number-small-1","carbon:number-small-2","carbon:number-small-3","carbon:number-small-4","carbon:number-small-5","carbon:number-small-6","carbon:number-small-7","carbon:number-small-8","carbon:number-small-9","carbon:object-storage","carbon:object-storage-alt","carbon:observed-hail","carbon:observed-lightning","carbon:omega","carbon:opacity","carbon:open-panel-bottom","carbon:open-panel-filled-bottom","carbon:open-panel-filled-left","carbon:open-panel-filled-right","carbon:open-panel-filled-top","carbon:open-panel-left","carbon:open-panel-right","carbon:open-panel-top","carbon:operation","carbon:operation-gauge","carbon:operation-if","carbon:operations-field","carbon:operations-record","carbon:order-details","carbon:ordinal","carbon:outage","carbon:outlook-severe","carbon:overflow-menu-horizontal","carbon:overflow-menu-vertical","carbon:overlay","carbon:package","carbon:package-text-analysis","carbon:page-break","carbon:page-first","carbon:page-last","carbon:page-number","carbon:page-scroll","carbon:paint-brush","carbon:paint-brush-alt","carbon:palm-tree","carbon:pan-horizontal","carbon:pan-vertical","carbon:panel-expansion","carbon:paragraph","carbon:parameter","carbon:parent-child","carbon:partition-auto","carbon:partition-collection","carbon:partition-repartition","carbon:partition-same","carbon:partition-specific","carbon:partly-cloudy","carbon:partly-cloudy-night","carbon:partnership","carbon:passenger-drinks","carbon:passenger-plus","carbon:password","carbon:paste","carbon:pause","carbon:pause-filled","carbon:pause-future","carbon:pause-outline","carbon:pause-outline-filled","carbon:pause-past","carbon:pcn-e-node","carbon:pcn-military","carbon:pcn-p-node","carbon:pcn-z-node","carbon:pdf","carbon:pdf-reference","carbon:pedestrian","carbon:pedestrian-child","carbon:pedestrian-family","carbon:pen","carbon:pen-fountain","carbon:pending","carbon:pending-filled","carbon:percentage","carbon:percentage-filled","carbon:person","carbon:person-favorite","carbon:pest","carbon:pet-image-b","carbon:pet-image-o","carbon:phone","carbon:phone-application","carbon:phone-block","carbon:phone-block-filled","carbon:phone-filled","carbon:phone-incoming","carbon:phone-incoming-filled","carbon:phone-ip","carbon:phone-off","carbon:phone-off-filled","carbon:phone-outgoing","carbon:phone-outgoing-filled","carbon:phone-settings","carbon:phone-voice","carbon:phone-voice-filled","carbon:phrase-sentiment","carbon:picnic-area","carbon:piggy-bank","carbon:piggy-bank-slot","carbon:pills","carbon:pills-add","carbon:pills-subtract","carbon:pin","carbon:pin-filled","carbon:plan","carbon:plane","carbon:plane-private","carbon:plane-sea","carbon:play","carbon:play-filled","carbon:play-filled-alt","carbon:play-outline","carbon:play-outline-filled","carbon:playlist","carbon:plug","carbon:plug-filled","carbon:png","carbon:point-of-presence","carbon:pointer-text","carbon:police","carbon:policy","carbon:popup","carbon:port-input","carbon:port-output","carbon:portfolio","carbon:power","carbon:ppt","carbon:presentation-file","carbon:pressure","carbon:pressure-filled","carbon:previous-filled","carbon:previous-outline","carbon:printer","carbon:product","carbon:progress-bar","carbon:progress-bar-round","carbon:promote","carbon:property-relationship","carbon:purchase","carbon:qc-launch","carbon:qq-plot","carbon:qr-code","carbon:quadrant-plot","carbon:query","carbon:query-queue","carbon:queued","carbon:quotes","carbon:radar","carbon:radar-enhanced","carbon:radar-weather","carbon:radio","carbon:radio-button","carbon:radio-button-checked","carbon:radio-combat","carbon:radio-push-to-talk","carbon:rain","carbon:rain-drizzle","carbon:rain-drop","carbon:rain-heavy","carbon:rain-scattered","carbon:rain-scattered-night","carbon:raw","carbon:receipt","carbon:recently-viewed","carbon:recommend","carbon:recording","carbon:recording-filled","carbon:recording-filled-alt","carbon:recycle","carbon:redo","carbon:ref-evapotranspiration","carbon:reference-architecture","carbon:reflect-horizontal","carbon:reflect-vertical","carbon:region-analysis-area","carbon:region-analysis-volume","carbon:registration","carbon:reminder","carbon:reminder-medical","carbon:renew","carbon:repeat","carbon:repeat-one","carbon:replicate","carbon:reply","carbon:reply-all","carbon:repo-artifact","carbon:repo-source-code","carbon:report","carbon:report-data","carbon:request-quote","carbon:research-bloch-sphere","carbon:research-hinton-plot","carbon:research-matrix","carbon:reset","carbon:reset-alt","carbon:restart","carbon:restaurant","carbon:restaurant-fine","carbon:result","carbon:result-draft","carbon:result-new","carbon:result-old","carbon:retry-failed","carbon:review","carbon:rewind-10","carbon:rewind-30","carbon:rewind-5","carbon:road","carbon:road-weather","carbon:roadmap","carbon:rocket","carbon:rotate","carbon:rotate-180","carbon:rotate-360","carbon:rotate-clockwise","carbon:rotate-clockwise-alt","carbon:rotate-clockwise-alt-filled","carbon:rotate-clockwise-filled","carbon:rotate-counterclockwise","carbon:rotate-counterclockwise-alt","carbon:rotate-counterclockwise-alt-filled","carbon:rotate-counterclockwise-filled","carbon:router","carbon:router-voice","carbon:router-wifi","carbon:row","carbon:row-collapse","carbon:row-delete","carbon:row-expand","carbon:row-insert","carbon:rss","carbon:rule","carbon:rule-cancelled","carbon:rule-data-quality","carbon:rule-draft","carbon:rule-filled","carbon:rule-locked","carbon:rule-partial","carbon:rule-test","carbon:ruler","carbon:ruler-alt","carbon:run","carbon:run-mirror","carbon:s","carbon:s-alt","carbon:sailboat-coastal","carbon:sailboat-offshore","carbon:sankey-diagram","carbon:sankey-diagram-alt","carbon:satellite","carbon:satellite-radar","carbon:satellite-weather","carbon:save","carbon:save-annotation","carbon:save-image","carbon:save-model","carbon:save-series","carbon:scale","carbon:scales","carbon:scales-tipped","carbon:scalpel","carbon:scalpel-cursor","carbon:scalpel-lasso","carbon:scalpel-select","carbon:scan","carbon:scan-alt","carbon:scan-disabled","carbon:scatter-matrix","carbon:schematics","carbon:scis-control-tower","carbon:scis-transparent-supply","carbon:scooter","carbon:scooter-front","carbon:screen","carbon:screen-map","carbon:screen-map-set","carbon:screen-off","carbon:script","carbon:script-reference","carbon:sdk","carbon:search","carbon:search-advanced","carbon:search-locate","carbon:search-locate-mirror","carbon:security","carbon:security-services","carbon:select-01","carbon:select-02","carbon:select-window","carbon:send","carbon:send-alt","carbon:send-alt-filled","carbon:send-backward","carbon:send-filled","carbon:send-to-back","carbon:server-dns","carbon:server-proxy","carbon:server-time","carbon:service-desk","carbon:service-id","carbon:session-border-control","carbon:settings","carbon:settings-adjust","carbon:settings-check","carbon:settings-services","carbon:settings-view","carbon:shape-except","carbon:shape-exclude","carbon:shape-intersect","carbon:shape-join","carbon:shape-unite","carbon:share","carbon:share-knowledge","carbon:shopping-bag","carbon:shopping-cart","carbon:shopping-cart-arrow-down","carbon:shopping-cart-arrow-up","carbon:shopping-cart-clear","carbon:shopping-cart-error","carbon:shopping-cart-minus","carbon:shopping-cart-plus","carbon:shopping-catalog","carbon:show-data-cards","carbon:shrink-screen","carbon:shrink-screen-filled","carbon:shuffle","carbon:shuttle","carbon:side-panel-close","carbon:side-panel-close-filled","carbon:side-panel-open","carbon:side-panel-open-filled","carbon:sight","carbon:sigma","carbon:signal-strength","carbon:sim-card","carbon:skill-level","carbon:skill-level-advanced","carbon:skill-level-basic","carbon:skill-level-intermediate","carbon:skip-back","carbon:skip-back-filled","carbon:skip-back-outline","carbon:skip-back-outline-filled","carbon:skip-back-outline-solid","carbon:skip-back-solid-filled","carbon:skip-forward","carbon:skip-forward-filled","carbon:skip-forward-outline","carbon:skip-forward-outline-filled","carbon:skip-forward-outline-solid","carbon:skip-forward-solid-filled","carbon:sleet","carbon:slisor","carbon:slm","carbon:smell","carbon:smoke","carbon:smoothing","carbon:smoothing-cursor","carbon:snooze","carbon:snow","carbon:snow-blizzard","carbon:snow-density","carbon:snow-heavy","carbon:snow-scattered","carbon:snow-scattered-night","carbon:snowflake","carbon:soccer","carbon:software-resource","carbon:software-resource-cluster","carbon:software-resource-resource","carbon:soil-moisture","carbon:soil-moisture-field","carbon:soil-moisture-global","carbon:soil-temperature","carbon:soil-temperature-field","carbon:soil-temperature-global","carbon:solar-panel","carbon:sort-ascending","carbon:sort-descending","carbon:sort-remove","carbon:spell-check","carbon:spine-label","carbon:split","carbon:split-discard","carbon:split-screen","carbon:spray-paint","carbon:sprout","carbon:sql","carbon:stack-limitation","carbon:stacked-move","carbon:stacked-scrolling-1","carbon:stacked-scrolling-2","carbon:stamp","carbon:star","carbon:star-filled","carbon:star-half","carbon:star-review","carbon:status-acknowledge","carbon:status-change","carbon:status-partial-fail","carbon:status-resolved","carbon:stay-inside","carbon:stem-leaf-plot","carbon:stethoscope","carbon:stop","carbon:stop-filled","carbon:stop-filled-alt","carbon:stop-outline","carbon:stop-outline-filled","carbon:stop-sign","carbon:stop-sign-filled","carbon:storage-pool","carbon:storage-request","carbon:store","carbon:storm-tracker","carbon:strawberry","carbon:stress-breath-editor","carbon:string-integer","carbon:string-text","carbon:study-next","carbon:study-previous","carbon:study-read","carbon:study-skip","carbon:study-transfer","carbon:study-unread","carbon:study-view","carbon:sub-volume","carbon:subflow","carbon:subflow-local","carbon:subnet-acl-rules","carbon:subtract","carbon:subtract-alt","carbon:summary-kpi","carbon:summary-kpi-mirror","carbon:sun","carbon:sunrise","carbon:sunset","carbon:support-vector-machine","carbon:svg","carbon:swim","carbon:switch-layer-2","carbon:switch-layer-3","carbon:switcher","carbon:sys-provision","carbon:t","carbon:t-alt","carbon:table","carbon:table-alias","carbon:table-built","carbon:table-of-contents","carbon:table-shortcut","carbon:table-split","carbon:tablet","carbon:tablet-landscape","carbon:tag","carbon:tag-edit","carbon:tag-export","carbon:tag-group","carbon:tag-import","carbon:tag-none","carbon:tank","carbon:task","carbon:task-add","carbon:task-approved","carbon:task-asset-view","carbon:task-complete","carbon:task-location","carbon:task-remove","carbon:task-settings","carbon:task-star","carbon:task-tools","carbon:task-view","carbon:taste","carbon:taxi","carbon:tcp-ip-service","carbon:temperature","carbon:temperature-celsius","carbon:temperature-celsius-alt","carbon:temperature-fahrenheit","carbon:temperature-fahrenheit-alt","carbon:temperature-feels-like","carbon:temperature-frigid","carbon:temperature-hot","carbon:temperature-inversion","carbon:temperature-max","carbon:temperature-min","carbon:temperature-water","carbon:template","carbon:tennis","carbon:tennis-ball","carbon:term","carbon:terminal","carbon:terminal-3270","carbon:test-tool","carbon:text-align-center","carbon:text-align-justify","carbon:text-align-left","carbon:text-align-mixed","carbon:text-align-right","carbon:text-all-caps","carbon:text-annotation-toggle","carbon:text-bold","carbon:text-clear-format","carbon:text-color","carbon:text-creation","carbon:text-fill","carbon:text-font","carbon:text-footnote","carbon:text-highlight","carbon:text-indent","carbon:text-indent-less","carbon:text-indent-more","carbon:text-italic","carbon:text-kerning","carbon:text-leading","carbon:text-line-spacing","carbon:text-link","carbon:text-link-analysis","carbon:text-mining","carbon:text-mining-applier","carbon:text-new-line","carbon:text-scale","carbon:text-selection","carbon:text-small-caps","carbon:text-strikethrough","carbon:text-subscript","carbon:text-superscript","carbon:text-tracking","carbon:text-underline","carbon:text-vertical-alignment","carbon:text-wrap","carbon:theater","carbon:this-side-up","carbon:threshold","carbon:thumbnail-1","carbon:thumbnail-2","carbon:thumbnail-preview","carbon:thumbs-down","carbon:thumbs-down-filled","carbon:thumbs-up","carbon:thumbs-up-filled","carbon:thunderstorm","carbon:thunderstorm-scattered","carbon:thunderstorm-scattered-night","carbon:thunderstorm-severe","carbon:thunderstorm-strong","carbon:ticket","carbon:tides","carbon:tif","carbon:time","carbon:time-plot","carbon:timer","carbon:tool-box","carbon:tool-kit","carbon:tools","carbon:tools-alt","carbon:tornado","carbon:tornado-warning","carbon:touch-1","carbon:touch-1-down","carbon:touch-1-down-filled","carbon:touch-1-filled","carbon:touch-2","carbon:touch-2-filled","carbon:touch-interaction","carbon:traffic-cone","carbon:traffic-event","carbon:traffic-flow","carbon:traffic-flow-incident","carbon:traffic-incident","carbon:traffic-weather-incident","carbon:train","carbon:train-heart","carbon:train-profile","carbon:train-speed","carbon:train-ticket","carbon:train-time","carbon:tram","carbon:transform-binary","carbon:transform-instructions","carbon:transform-language","carbon:transgender","carbon:translate","carbon:transmission-lte","carbon:transpose","carbon:trash-can","carbon:tree","carbon:tree-fall-risk","carbon:tree-view","carbon:tree-view-alt","carbon:trophy","carbon:trophy-filled","carbon:tropical-storm","carbon:tropical-storm-model-tracks","carbon:tropical-storm-tracks","carbon:tropical-warning","carbon:tsq","carbon:tsunami","carbon:tsv","carbon:two-factor-authentication","carbon:two-person-lift","carbon:txt","carbon:txt-reference","carbon:type-pattern","carbon:types","carbon:u1","carbon:u2","carbon:u3","carbon:umbrella","carbon:undefined","carbon:undefined-filled","carbon:undo","carbon:ungroup-objects","carbon:unknown","carbon:unknown-filled","carbon:unlink","carbon:unlocked","carbon:unsaved","carbon:up-to-top","carbon:update-now","carbon:upgrade","carbon:upload","carbon:usb","carbon:user","carbon:user-access","carbon:user-activity","carbon:user-admin","carbon:user-avatar","carbon:user-avatar-filled","carbon:user-avatar-filled-alt","carbon:user-certification","carbon:user-data","carbon:user-favorite","carbon:user-favorite-alt","carbon:user-favorite-alt-filled","carbon:user-filled","carbon:user-follow","carbon:user-identification","carbon:user-military","carbon:user-multiple","carbon:user-online","carbon:user-profile","carbon:user-profile-alt","carbon:user-role","carbon:user-service-desk","carbon:user-settings","carbon:user-simulation","carbon:user-speaker","carbon:user-sponsor","carbon:user-x-ray","carbon:uv-index","carbon:uv-index-alt","carbon:uv-index-filled","carbon:value-variable","carbon:van","carbon:vegetation-asset","carbon:vehicle-api","carbon:vehicle-connected","carbon:vehicle-insights","carbon:vehicle-services","carbon:version","carbon:version-major","carbon:version-minor","carbon:version-patch","carbon:vertical-view","carbon:video","carbon:video-add","carbon:video-chat","carbon:video-filled","carbon:video-off","carbon:video-off-filled","carbon:view","carbon:view-filled","carbon:view-mode-1","carbon:view-mode-2","carbon:view-next","carbon:view-off","carbon:view-off-filled","carbon:virtual-column","carbon:virtual-column-key","carbon:virtual-desktop","carbon:virtual-machine","carbon:virtual-private-cloud","carbon:virtual-private-cloud-alt","carbon:visual-recognition","carbon:vlan","carbon:vlan-ibm","carbon:vmdk-disk","carbon:voice-activate","carbon:voicemail","carbon:volume-block-storage","carbon:volume-down","carbon:volume-down-alt","carbon:volume-down-filled","carbon:volume-down-filled-alt","carbon:volume-file-storage","carbon:volume-mute","carbon:volume-mute-filled","carbon:volume-object-storage","carbon:volume-up","carbon:volume-up-alt","carbon:volume-up-filled","carbon:volume-up-filled-alt","carbon:vpn","carbon:vpn-connection","carbon:vpn-policy","carbon:wallet","carbon:warning","carbon:warning-alt","carbon:warning-alt-filled","carbon:warning-alt-inverted","carbon:warning-alt-inverted-filled","carbon:warning-filled","carbon:warning-hex","carbon:warning-hex-filled","carbon:warning-other","carbon:warning-square","carbon:warning-square-filled","carbon:watch","carbon:watson","carbon:watson-machine-learning","carbon:wave-direction","carbon:wave-height","carbon:wave-period","carbon:weather-front-cold","carbon:weather-front-stationary","carbon:weather-front-warm","carbon:weather-station","carbon:webhook","carbon:websheet","carbon:wheat","carbon:white-paper","carbon:wifi","carbon:wifi-bridge","carbon:wifi-bridge-alt","carbon:wifi-controller","carbon:wifi-not-secure","carbon:wifi-off","carbon:wifi-secure","carbon:wikis","carbon:wind-gusts","carbon:wind-power","carbon:wind-stream","carbon:window-auto","carbon:window-base","carbon:window-black-saturation","carbon:window-overlay","carbon:window-preset","carbon:windy","carbon:windy-dust","carbon:windy-snow","carbon:windy-strong","carbon:winter-warning","carbon:wintry-mix","carbon:wireless-checkout","carbon:wmv","carbon:word-cloud","carbon:workflow-automation","carbon:workspace","carbon:workspace-import","carbon:worship","carbon:worship-christian","carbon:worship-jewish","carbon:worship-muslim","carbon:x","carbon:x-axis","carbon:xls","carbon:xml","carbon:y","carbon:y-axis","carbon:z","carbon:z-axis","carbon:z-lpar","carbon:z-systems","carbon:zip","carbon:zip-reference","carbon:zoom-area","carbon:zoom-fit","carbon:zoom-in","carbon:zoom-in-area","carbon:zoom-out","carbon:zoom-out-area","carbon:zoom-pan","carbon:zoom-reset","carbon:zos","carbon:zos-sysplex","carbon:app-switcher","carbon:arrows","carbon:back-to-top","carbon:checkbox-undeterminate","carbon:cloud-lightning","carbon:cloud-rain","carbon:cloud-snow","carbon:delete","carbon:letter-aa-large","carbon:sunny","simple-icons:tiktok","ph:map-pin-fill","mdi:map-marker"] as const diff --git a/packages/ui/package.json b/packages/ui/package.json index 77de2c61a1..4dd2614cef 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -55,6 +55,7 @@ "@faker-js/faker": "7.6.0", "@geometricpanda/storybook-addon-badges": "2.0.0", "@iconify-json/carbon": "1.1.19", + "@iconify-json/mdi": "1.1.54", "@iconify-json/ph": "1.1.6", "@iconify-json/simple-icons": "1.1.65", "@iconify/react": "4.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5e4fcf3e5..3a366e8089 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1458,6 +1458,9 @@ importers: '@iconify-json/carbon': specifier: 1.1.19 version: 1.1.19 + '@iconify-json/mdi': + specifier: 1.1.54 + version: 1.1.54 '@iconify-json/ph': specifier: 1.1.6 version: 1.1.6 @@ -5641,6 +5644,12 @@ packages: '@iconify/types': 2.0.0 dev: true + /@iconify-json/mdi@1.1.54: + resolution: {integrity: sha512-3QAsxte90EalbN2e8J30OqSSZz9qN2x+kmykQwsPahoW2dOtSvj+BR9YdiUd9A5XKk2nuU4UH5Gj/cq6WZ6CzQ==} + dependencies: + '@iconify/types': 2.0.0 + dev: true + /@iconify-json/ph@1.1.6: resolution: {integrity: sha512-dexzEndlXQX/sbQhnEpA94Pby6JCGV2tZToSGcPPQpbilDGyk5VMd0ymusYoocRAn6+qLpGRvMoz5XFKGqP+VA==} dependencies: From aeee86d26b950b4b82e3bb4ca27fd255e6c61db2 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Tue, 8 Aug 2023 22:33:34 -0400 Subject: [PATCH 40/63] move to built-in SWC --- packages/ui/.storybook/main.ts | 43 +++++++--------------------------- packages/ui/.swcrc | 21 +++++++++++++++++ 2 files changed, 29 insertions(+), 35 deletions(-) create mode 100644 packages/ui/.swcrc diff --git a/packages/ui/.storybook/main.ts b/packages/ui/.storybook/main.ts index 853fc10421..8763a0fc05 100644 --- a/packages/ui/.storybook/main.ts +++ b/packages/ui/.storybook/main.ts @@ -2,18 +2,20 @@ /* eslint-disable node/no-process-env */ import { type StorybookConfig } from '@storybook/nextjs' import isChromatic from 'chromatic/isChromatic' +import dotenv from 'dotenv' import { mergeAndConcat } from 'merge-anything' import { type PropItem } from 'react-docgen-typescript' -import * as path from 'path' +import path from 'path' const filePattern = '*.stories.@(ts|tsx)' // eslint-disable-next-line @typescript-eslint/no-unused-vars const isDev = process.env.NODE_ENV === 'development' +dotenv.config({ path: path.resolve(__dirname, '../../../.env') }) + const config: StorybookConfig = { - // stories: [`../!(node_modules)/**/${filePattern}`, '../other/**/*.mdx'], stories: [`../(components|hooks|icon|layouts|modals|other)/**/${filePattern}`, '../other/**/*.mdx'], staticDirs: [ { @@ -29,8 +31,6 @@ const config: StorybookConfig = { '@tomfreudenberg/next-auth-mock/storybook', '@storybook/addon-designs', 'storybook-addon-pseudo-states', - // 'css-chaos-addon', - 'storybook-addon-swc', '@storybook/addon-essentials', // Keep this last ], framework: { @@ -39,6 +39,7 @@ const config: StorybookConfig = { builder: { lazyCompilation: Boolean(process.env.SB_LAZY), fsCache: Boolean(process.env.SB_CACHE), + useSWC: true, }, nextConfigPath: path.resolve(__dirname, '../../../apps/app/next.config.mjs'), fastRefresh: true, @@ -61,16 +62,14 @@ const config: StorybookConfig = { allowSyntheticDefaultImports: false, esModuleInterop: false, }, - // tsconfigPath: path.resolve(__dirname, '../tsconfig.storybook.json'), exclude: ['node_modules'], propFilter: (prop: PropItem) => { const pathTest = /node_modules\/(?!(?:\.pnpm\/)?@mantine(?!.?styles))/ - // if (prop.parent && !pathTest.test(prop.parent.fileName)) console.log(prop) return prop.parent ? !pathTest.test(prop.parent.fileName) : true }, }, }, - webpackFinal: (config) => { + webpackFinal: (config, options) => { const configAdditions: typeof config = { resolve: { alias: { @@ -82,37 +81,11 @@ const config: StorybookConfig = { 'next-i18next': 'react-i18next', }, roots: [path.resolve(__dirname, '../../../apps/app/public')], - // fallback: { - // fs: false, - // assert: false, - // buffer: false, - // console: false, - // constants: false, - // crypto: false, - // domain: false, - // events: false, - // http: false, - // https: false, - // os: false, - // path: false, - // punycode: false, - // process: false, - // querystring: false, - // stream: false, - // string_decoder: false, - // sys: false, - // timers: false, - // tty: false, - // url: false, - // util: false, - // v8: false, - // vm: false, - // zlib: false, - // }, }, stats: { colors: true, }, + devtool: options.configType === 'DEVELOPMENT' ? 'eval-source-map' : undefined, } const mergedConfig = mergeAndConcat(config, configAdditions) return mergedConfig @@ -124,6 +97,6 @@ const config: StorybookConfig = { ? { SKIP_ENV_VALIDATION: 'true', } - : {}, + : { NEXT_PUBLIC_GOOGLE_MAPS_API: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API as string }, } export default config diff --git a/packages/ui/.swcrc b/packages/ui/.swcrc new file mode 100644 index 0000000000..bfa87d4f85 --- /dev/null +++ b/packages/ui/.swcrc @@ -0,0 +1,21 @@ +{ + "$schema": "https://json.schemastore.org/swcrc", + "jsc": { + "parser": { + "syntax": "typescript", + "tsx": true, + "dynamicImport": true + }, + "transform": { + "react": { + "runtime": "automatic", + "refresh": true + } + }, + "target": "es2020", + "loose": false, + "externalHelpers": false, + "keepClassNames": false + }, + "minify": false +} From eaed7ae429181ca130647aafb810caff74ce1ee2 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Wed, 9 Aug 2023 13:53:37 -0400 Subject: [PATCH 41/63] remove try/catch from handlers, fix schema/type issues --- packages/api/router/attribute/index.ts | 2 +- .../query.getFilterOptions.handler.ts | 44 ++-- .../router/attribute/query.getOne.handler.ts | 43 ++-- packages/api/router/fieldOpt/index.ts | 2 - .../query.attributeCategories.handler.ts | 31 +-- .../query.attributesByCategory.handler.ts | 31 +-- .../fieldOpt/query.countries.handler.ts | 43 ++-- .../query.countryGovDistMap.handler.ts | 61 +++-- .../query.govDistsByCountry.handler.ts | 97 ++++---- .../query.govDistsByCountryNoSub.handler.ts | 61 +++-- .../fieldOpt/query.languages.handler.ts | 33 ++- .../fieldOpt/query.phoneTypes.handler.ts | 25 +- .../fieldOpt/query.userTitle.handler.ts | 15 +- .../router/geo/query.autocomplete.handler.ts | 49 ++-- .../router/geo/query.geoByPlaceId.handler.ts | 21 +- .../internalNote/mutation.create.handler.ts | 29 +-- .../router/internalNote/query.byId.handler.ts | 9 +- .../query.getAllForRecord.handler.ts | 9 +- .../location/mutation.create.handler.ts | 11 +- .../location/mutation.update.handler.ts | 45 ++-- .../router/location/mutation.update.schema.ts | 4 +- .../location/query.forLocationCard.handler.ts | 79 +++--- .../location/query.forLocationPage.handler.ts | 56 ++--- .../location/query.forVisitCard.handler.ts | 52 ++-- .../location/query.getAddress.handler.ts | 83 +++---- .../router/location/query.getById.handler.ts | 83 +++---- .../location/query.getByOrgId.handler.ts | 85 ++++--- .../location/query.getNameById.handler.ts | 15 +- .../router/location/query.getNames.handler.ts | 25 +- .../orgEmail/mutation.create.handler.ts | 23 +- .../orgEmail/mutation.update.handler.ts | 41 ++-- .../orgEmail/mutation.upsertMany.handler.ts | 125 +++++----- .../orgEmail/query.forContactInfo.handler.ts | 83 +++---- .../api/router/orgEmail/query.get.handler.ts | 91 ++++--- .../api/router/orgEmail/query.get.schema.ts | 24 +- .../orgHours/mutation.create.handler.ts | 23 +- .../orgHours/mutation.createMany.handler.ts | 29 +-- .../orgHours/mutation.update.handler.ts | 41 ++-- .../orgHours/query.forHoursDisplay.handler.ts | 51 ++-- .../orgHours/query.forHoursDrawer.handler.ts | 79 +++--- .../router/orgHours/query.getTz.handler.ts | 14 +- .../orgPhone/mutation.create.handler.ts | 23 +- .../orgPhone/mutation.update.handler.ts | 41 ++-- .../orgPhone/mutation.upsertMany.handler.ts | 145 ++++++----- .../orgPhone/query.forContactInfo.handler.ts | 83 +++---- .../api/router/orgPhone/query.get.handler.ts | 91 ++++--- .../api/router/orgPhone/query.get.schema.ts | 24 +- .../orgPhoto/mutation.create.handler.ts | 23 +- .../orgPhoto/mutation.update.handler.ts | 41 ++-- .../orgPhoto/query.getByParent.handler.ts | 51 ++-- .../orgSocialMedia/mutation.create.handler.ts | 23 +- .../orgSocialMedia/mutation.update.handler.ts | 41 ++-- .../query.forContactInfo.handler.ts | 65 +++-- .../orgWebsite/mutation.create.handler.ts | 23 +- .../orgWebsite/mutation.update.handler.ts | 41 ++-- .../query.forContactInfo.handler.ts | 71 +++--- .../mutation.attachAttribute.handler.ts | 41 ++-- .../mutation.createNewQuick.handler.ts | 21 +- .../mutation.createNewSuggestion.handler.ts | 21 +- .../query.checkForExisting.handler.ts | 31 +-- .../query.forLocationPage.handler.ts | 47 ++-- .../organization/query.forOrgPage.handler.ts | 89 ++++--- .../query.generateSlug.handler.ts | 9 +- .../organization/query.getById.handler.ts | 35 ++- .../organization/query.getBySlug.handler.ts | 37 ++- .../query.getIdFromSlug.handler.ts | 23 +- .../query.getIntlCrisis.handler.ts | 153 ++++++------ .../query.getNameFromSlug.handler.ts | 23 +- .../query.getNatlCrisis.handler.ts | 160 ++++++------- .../organization/query.isSaved.handler.ts | 43 ++-- .../query.searchDistance.handler.ts | 35 ++- .../organization/query.searchName.handler.ts | 36 ++- .../query.slugRedirect.handler.ts | 29 +-- .../query.suggestionOptions.handler.ts | 83 +++---- .../mutation.updateEmailData.handler.ts | 55 ++--- .../mutation.updatePhoneData.handler.ts | 55 ++--- ...ation.updateServiceLocationData.handler.ts | 39 ++- .../quicklink/query.getEmailData.handler.ts | 139 ++++++----- .../quicklink/query.getPhoneData.handler.ts | 149 ++++++------ .../query.getServiceLocationData.handler.ts | 107 ++++----- .../router/review/mutation.create.handler.ts | 31 +-- .../router/review/mutation.delete.handler.ts | 41 ++-- .../router/review/mutation.hide.handler.ts | 41 ++-- .../review/mutation.unDelete.handler.ts | 41 ++-- .../router/review/mutation.unHide.handler.ts | 41 ++-- .../router/review/query.getAverage.handler.ts | 53 ++--- .../router/review/query.getByIds.handler.ts | 165 +++++++------ .../review/query.getByLocation.handler.ts | 19 +- .../router/review/query.getByOrg.handler.ts | 17 +- .../review/query.getByService.handler.ts | 19 +- .../router/review/query.getByUser.handler.ts | 27 +-- .../review/query.getCurrentUser.handler.ts | 27 +-- .../review/query.getFeatured.handler.ts | 165 +++++++------ .../savedLists/mutation.create.handler.ts | 25 +- .../mutation.createAndSaveItem.handler.ts | 33 ++- .../savedLists/mutation.delete.handler.ts | 43 ++-- .../savedLists/mutation.deleteItem.handler.ts | 33 ++- .../savedLists/mutation.saveItem.handler.ts | 33 ++- .../savedLists/mutation.shareUrl.handler.ts | 37 ++- .../savedLists/mutation.unShareUrl.handler.ts | 49 ++-- .../router/savedLists/query.getAll.handler.ts | 37 ++- .../savedLists/query.getById.handler.ts | 47 ++-- .../savedLists/query.getByUrl.handler.ts | 25 +- .../savedLists/query.isSaved.handler.ts | 45 ++-- ...mutation.attachServiceAttribute.handler.ts | 49 ++-- .../mutation.attachServiceTags.handler.ts | 27 +-- .../router/service/mutation.create.handler.ts | 15 +- ...tation.createAccessInstructions.handler.ts | 41 ++-- .../mutation.createServiceArea.handler.ts | 53 ++--- .../service/mutation.linkEmails.handler.ts | 27 +-- .../service/mutation.linkPhones.handler.ts | 27 +-- .../router/service/mutation.update.handler.ts | 29 +-- .../api/router/service/query.byId.handler.ts | 223 +++++++++-------- .../router/service/query.byOrgId.handler.ts | 61 +++-- .../service/query.byOrgLocationId.handler.ts | 223 +++++++++-------- .../service/query.byUserListId.handler.ts | 225 +++++++++--------- .../service/query.forServiceDrawer.handler.ts | 113 +++++---- .../query.forServiceEditDrawer.handler.ts | 161 ++++++------- .../query.forServiceInfoCard.handler.ts | 73 +++--- .../service/query.forServiceModal.handler.ts | 85 ++++--- .../service/query.getFilterOptions.handler.ts | 57 ++--- .../router/service/query.getNames.handler.ts | 53 ++--- .../router/service/query.getNames.schema.ts | 4 +- .../service/query.getOptions.handler.ts | 35 ++- .../service/query.getParentName.handler.ts | 37 ++- .../service/query.getParentName.schema.ts | 4 +- packages/api/router/system/index.ts | 62 +---- ...updateInactiveCountryEdgeConfig.handler.ts | 53 +++++ packages/api/router/system/permission.ts | 38 ++- .../user/mutation.adminCreate.handler.ts | 35 ++- .../user/mutation.confirmAccount.handler.ts | 20 +- .../router/user/mutation.create.handler.ts | 3 +- .../user/mutation.deleteAccount.handler.ts | 32 ++- .../user/mutation.forgotPassword.handler.ts | 9 +- .../user/mutation.resetPassword.handler.ts | 13 +- .../user/mutation.submitSurvey.handler.ts | 9 +- .../query.getLocationPermissions.handler.ts | 25 +- .../user/query.getOrgPermissions.handler.ts | 25 +- .../user/query.getPermissions.handler.ts | 23 +- .../router/user/query.getProfile.handler.ts | 35 ++- .../user/query.surveyOptions.handler.ts | 104 ++++---- packages/api/schemas/common.ts | 2 +- .../components/data-portal/AddressDrawer.tsx | 8 +- .../data-portal/EmailTableDrawer.tsx | 16 +- .../data-portal/PhoneTableDrawer.tsx | 12 +- .../data-portal/ServiceEditDrawer.tsx | 12 +- .../ui/components/sections/LocationCard.tsx | 3 +- packages/ui/mockData/orgService.ts | 6 - packages/ui/mockData/service.ts | 21 +- .../modals/dataPortal/PhoneEmail/fields.tsx | 2 +- 150 files changed, 3310 insertions(+), 3987 deletions(-) create mode 100644 packages/api/router/system/mutation.updateInactiveCountryEdgeConfig.handler.ts diff --git a/packages/api/router/attribute/index.ts b/packages/api/router/attribute/index.ts index 66b3df8366..c7052d439e 100644 --- a/packages/api/router/attribute/index.ts +++ b/packages/api/router/attribute/index.ts @@ -16,7 +16,7 @@ export const attributeRouter = defineRouter({ ) if (!HandlerCache.getFilterOptions) throw new Error('Failed to load handler') - return HandlerCache.getFilterOptions({ ctx }) + return HandlerCache.getFilterOptions() }), getOne: staffProcedure.input(schema.ZGetOneSchema).query(async ({ ctx, input }) => { if (!HandlerCache.getOne) diff --git a/packages/api/router/attribute/query.getFilterOptions.handler.ts b/packages/api/router/attribute/query.getFilterOptions.handler.ts index 2ec3597580..2473a30773 100644 --- a/packages/api/router/attribute/query.getFilterOptions.handler.ts +++ b/packages/api/router/attribute/query.getFilterOptions.handler.ts @@ -1,31 +1,25 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' -import { type TRPCHandlerParams } from '~api/types/handler' -export const getFilterOptions = async (ctx: TRPCHandlerParams) => { - try { - const result = await prisma.attribute.findMany({ - where: { - AND: { - filterType: { - not: null, - }, - active: true, +export const getFilterOptions = async () => { + const result = await prisma.attribute.findMany({ + where: { + AND: { + filterType: { + not: null, }, + active: true, }, - select: { - id: true, - tsKey: true, - tsNs: true, - filterType: true, - }, - orderBy: { - tsKey: 'asc', - }, - }) + }, + select: { + id: true, + tsKey: true, + tsNs: true, + filterType: true, + }, + orderBy: { + tsKey: 'asc', + }, + }) - return result - } catch (error) { - handleError(error) - } + return result } diff --git a/packages/api/router/attribute/query.getOne.handler.ts b/packages/api/router/attribute/query.getOne.handler.ts index 28b05bc938..3a0f395a34 100644 --- a/packages/api/router/attribute/query.getOne.handler.ts +++ b/packages/api/router/attribute/query.getOne.handler.ts @@ -1,31 +1,26 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetOneSchema } from './query.getOne.schema' export const getOne = async ({ input }: TRPCHandlerParams) => { - try { - const result = await prisma.attribute.findUniqueOrThrow({ - where: input, - select: { - id: true, - tag: true, - tsKey: true, - tsNs: true, - icon: true, - iconBg: true, - active: true, - requireBoolean: true, - requireData: true, - requireDataSchema: { select: { definition: true } }, - requireGeo: true, - requireLanguage: true, - requireText: true, - }, - }) - return result - } catch (error) { - handleError(error) - } + const result = await prisma.attribute.findUniqueOrThrow({ + where: input, + select: { + id: true, + tag: true, + tsKey: true, + tsNs: true, + icon: true, + iconBg: true, + active: true, + requireBoolean: true, + requireData: true, + requireDataSchema: { select: { definition: true } }, + requireGeo: true, + requireLanguage: true, + requireText: true, + }, + }) + return result } diff --git a/packages/api/router/fieldOpt/index.ts b/packages/api/router/fieldOpt/index.ts index 85a5f3f064..0b3b7014e7 100644 --- a/packages/api/router/fieldOpt/index.ts +++ b/packages/api/router/fieldOpt/index.ts @@ -1,5 +1,3 @@ -import { z } from 'zod' - import { defineRouter, publicProcedure } from '~api/lib/trpc' import * as schema from './schemas' diff --git a/packages/api/router/fieldOpt/query.attributeCategories.handler.ts b/packages/api/router/fieldOpt/query.attributeCategories.handler.ts index af1932ceeb..a9c3d6e8eb 100644 --- a/packages/api/router/fieldOpt/query.attributeCategories.handler.ts +++ b/packages/api/router/fieldOpt/query.attributeCategories.handler.ts @@ -1,24 +1,19 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TAttributeCategoriesSchema } from './query.attributeCategories.schema' -export const attributeCategories = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const results = await prisma.attributeCategory.findMany({ - where: { active: true, ...(input?.length ? { tag: { in: input } } : {}) }, - select: { - id: true, - tag: true, - name: true, - icon: true, - intDesc: true, - }, - orderBy: { tag: 'asc' }, - }) - return results - } catch (error) { - handleError(error) - } +export const attributeCategories = async ({ input }: TRPCHandlerParams) => { + const results = await prisma.attributeCategory.findMany({ + where: { active: true, ...(input?.length ? { tag: { in: input } } : {}) }, + select: { + id: true, + tag: true, + name: true, + icon: true, + intDesc: true, + }, + orderBy: { tag: 'asc' }, + }) + return results } diff --git a/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts b/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts index 96dc71fc91..14a94b78bc 100644 --- a/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts +++ b/packages/api/router/fieldOpt/query.attributesByCategory.handler.ts @@ -3,30 +3,25 @@ import { type SetOptional } from 'type-fest' import { prisma } from '@weareinreach/db' import { type AttributesByCategory } from '@weareinreach/db/client' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TAttributesByCategorySchema } from './query.attributesByCategory.schema' export const attributesByCategory = async ({ input }: TRPCHandlerParams) => { - try { - const where = Array.isArray(input) - ? { categoryName: { in: input } } - : typeof input === 'string' - ? { categoryName: input } - : undefined - const result = await prisma.attributesByCategory.findMany({ - where, - orderBy: [{ categoryName: 'asc' }, { attributeName: 'asc' }], - }) + const where = Array.isArray(input) + ? { categoryName: { in: input } } + : typeof input === 'string' + ? { categoryName: input } + : undefined + const result = await prisma.attributesByCategory.findMany({ + where, + orderBy: [{ categoryName: 'asc' }, { attributeName: 'asc' }], + }) - const flushedResults = result.map((item) => - flush(item) - ) as FlushedAttributesByCategory[] - return flushedResults - } catch (error) { - handleError(error) - } + const flushedResults = result.map((item) => + flush(item) + ) as FlushedAttributesByCategory[] + return flushedResults } type FlushedAttributesByCategory = SetOptional< AttributesByCategory, diff --git a/packages/api/router/fieldOpt/query.countries.handler.ts b/packages/api/router/fieldOpt/query.countries.handler.ts index 5671ad465f..a1cb037afe 100644 --- a/packages/api/router/fieldOpt/query.countries.handler.ts +++ b/packages/api/router/fieldOpt/query.countries.handler.ts @@ -1,31 +1,26 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCountriesSchema } from './query.countries.schema' export const countries = async ({ input }: TRPCHandlerParams) => { - try { - const { where } = input ?? {} - const result = await prisma.country.findMany({ - where, - select: { - id: true, - cca2: true, - name: true, - dialCode: true, - flag: true, - tsKey: true, - tsNs: true, - activeForOrgs: true, - }, - orderBy: { - name: 'asc', - }, - }) - type CountryResult = (typeof result)[number][] - return result as CountryResult - } catch (error) { - handleError(error) - } + const { where } = input ?? {} + const result = await prisma.country.findMany({ + where, + select: { + id: true, + cca2: true, + name: true, + dialCode: true, + flag: true, + tsKey: true, + tsNs: true, + activeForOrgs: true, + }, + orderBy: { + name: 'asc', + }, + }) + type CountryResult = (typeof result)[number][] + return result as CountryResult } diff --git a/packages/api/router/fieldOpt/query.countryGovDistMap.handler.ts b/packages/api/router/fieldOpt/query.countryGovDistMap.handler.ts index 664663dcd7..064e9af05d 100644 --- a/packages/api/router/fieldOpt/query.countryGovDistMap.handler.ts +++ b/packages/api/router/fieldOpt/query.countryGovDistMap.handler.ts @@ -1,41 +1,36 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' export const countryGovDistMap = async () => { - try { - const fields = { id: true, tsKey: true, tsNs: true } + const fields = { id: true, tsKey: true, tsNs: true } - const countries = await prisma.country.findMany({ - where: { activeForOrgs: true }, - select: { - ...fields, - govDist: { select: fields }, + const countries = await prisma.country.findMany({ + where: { activeForOrgs: true }, + select: { + ...fields, + govDist: { select: fields }, + }, + }) + const govDists = await prisma.govDist.findMany({ + select: { + ...fields, + subDistricts: { + select: fields, }, - }) - const govDists = await prisma.govDist.findMany({ - select: { - ...fields, - subDistricts: { - select: fields, - }, - parent: { select: fields }, - country: { select: fields }, - }, - }) - const resultMap = new Map([ - ...(countries.map(({ govDist, ...rest }) => [rest.id, { ...rest, children: govDist }]) satisfies [ - string, - CountryGovDistMapItem, - ][]), - ...(govDists.map(({ subDistricts, parent, country, ...rest }) => [ - rest.id, - { ...rest, children: subDistricts, parent: parent ? { ...parent, parent: country } : country }, - ]) satisfies [string, CountryGovDistMapItem][]), - ]) - return resultMap - } catch (error) { - handleError(error) - } + parent: { select: fields }, + country: { select: fields }, + }, + }) + const resultMap = new Map([ + ...(countries.map(({ govDist, ...rest }) => [rest.id, { ...rest, children: govDist }]) satisfies [ + string, + CountryGovDistMapItem, + ][]), + ...(govDists.map(({ subDistricts, parent, country, ...rest }) => [ + rest.id, + { ...rest, children: subDistricts, parent: parent ? { ...parent, parent: country } : country }, + ]) satisfies [string, CountryGovDistMapItem][]), + ]) + return resultMap } interface CountryGovDistMapItemBasic { diff --git a/packages/api/router/fieldOpt/query.govDistsByCountry.handler.ts b/packages/api/router/fieldOpt/query.govDistsByCountry.handler.ts index a09dbfe6c5..25d0581b92 100644 --- a/packages/api/router/fieldOpt/query.govDistsByCountry.handler.ts +++ b/packages/api/router/fieldOpt/query.govDistsByCountry.handler.ts @@ -1,65 +1,60 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGovDistsByCountrySchema } from './query.govDistsByCountry.schema' export const govDistsByCountry = async ({ input }: TRPCHandlerParams) => { - try { - const data = await prisma.country.findMany({ - where: { - cca2: input?.cca2, - activeForOrgs: input?.activeForOrgs ?? undefined, - activeForSuggest: input?.activeForSuggest ?? undefined, - }, - select: { - id: true, - tsKey: true, - tsNs: true, - cca2: true, - flag: true, - govDist: { - where: { isPrimary: true }, - select: { - id: true, - tsKey: true, - tsNs: true, - abbrev: true, - govDistType: { - select: { tsKey: true, tsNs: true }, - }, - subDistricts: { - select: { - id: true, - tsKey: true, - tsNs: true, - govDistType: { - select: { tsKey: true, tsNs: true }, - }, - subDistricts: { - select: { - id: true, - tsKey: true, - tsNs: true, - govDistType: { - select: { tsKey: true, tsNs: true }, - }, + const data = await prisma.country.findMany({ + where: { + cca2: input?.cca2, + activeForOrgs: input?.activeForOrgs ?? undefined, + activeForSuggest: input?.activeForSuggest ?? undefined, + }, + select: { + id: true, + tsKey: true, + tsNs: true, + cca2: true, + flag: true, + govDist: { + where: { isPrimary: true }, + select: { + id: true, + tsKey: true, + tsNs: true, + abbrev: true, + govDistType: { + select: { tsKey: true, tsNs: true }, + }, + subDistricts: { + select: { + id: true, + tsKey: true, + tsNs: true, + govDistType: { + select: { tsKey: true, tsNs: true }, + }, + subDistricts: { + select: { + id: true, + tsKey: true, + tsNs: true, + govDistType: { + select: { tsKey: true, tsNs: true }, }, - orderBy: { name: 'asc' }, }, + orderBy: { name: 'asc' }, }, - orderBy: { name: 'asc' }, }, + orderBy: { name: 'asc' }, }, - orderBy: { name: 'asc' }, }, + orderBy: { name: 'asc' }, }, - orderBy: { - cca2: 'asc', - }, - }) - return data - } catch (error) { - handleError(error) - } + }, + orderBy: { + cca2: 'asc', + }, + }) + return data } diff --git a/packages/api/router/fieldOpt/query.govDistsByCountryNoSub.handler.ts b/packages/api/router/fieldOpt/query.govDistsByCountryNoSub.handler.ts index 0bb419eadd..76717690a2 100644 --- a/packages/api/router/fieldOpt/query.govDistsByCountryNoSub.handler.ts +++ b/packages/api/router/fieldOpt/query.govDistsByCountryNoSub.handler.ts @@ -1,43 +1,38 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGovDistsByCountryNoSubSchema } from './query.govDistsByCountryNoSub.schema' export const govDistsByCountryNoSub = async ({ input }: TRPCHandlerParams) => { - try { - const data = await prisma.country.findMany({ - where: { - cca2: input?.cca2, - activeForOrgs: input?.activeForOrgs ?? undefined, - activeForSuggest: input?.activeForSuggest ?? undefined, - }, - select: { - id: true, - tsKey: true, - tsNs: true, - cca2: true, - flag: true, - govDist: { - where: { isPrimary: true }, - select: { - id: true, - tsKey: true, - tsNs: true, - abbrev: true, - govDistType: { - select: { tsKey: true, tsNs: true }, - }, + const data = await prisma.country.findMany({ + where: { + cca2: input?.cca2, + activeForOrgs: input?.activeForOrgs ?? undefined, + activeForSuggest: input?.activeForSuggest ?? undefined, + }, + select: { + id: true, + tsKey: true, + tsNs: true, + cca2: true, + flag: true, + govDist: { + where: { isPrimary: true }, + select: { + id: true, + tsKey: true, + tsNs: true, + abbrev: true, + govDistType: { + select: { tsKey: true, tsNs: true }, }, - orderBy: { name: 'asc' }, }, + orderBy: { name: 'asc' }, }, - orderBy: { - cca2: 'asc', - }, - }) - return data - } catch (error) { - handleError(error) - } + }, + orderBy: { + cca2: 'asc', + }, + }) + return data } diff --git a/packages/api/router/fieldOpt/query.languages.handler.ts b/packages/api/router/fieldOpt/query.languages.handler.ts index d3f24f12d9..ca1daf4443 100644 --- a/packages/api/router/fieldOpt/query.languages.handler.ts +++ b/packages/api/router/fieldOpt/query.languages.handler.ts @@ -1,25 +1,20 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TLanguagesSchema } from './query.languages.schema' -export const languages = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const results = await prisma.language.findMany({ - where: input, - select: { - id: true, - languageName: true, - localeCode: true, - iso6392: true, - nativeName: true, - activelyTranslated: true, - }, - orderBy: { languageName: 'asc' }, - }) - return results - } catch (error) { - handleError(error) - } +export const languages = async ({ input }: TRPCHandlerParams) => { + const results = await prisma.language.findMany({ + where: input, + select: { + id: true, + languageName: true, + localeCode: true, + iso6392: true, + nativeName: true, + activelyTranslated: true, + }, + orderBy: { languageName: 'asc' }, + }) + return results } diff --git a/packages/api/router/fieldOpt/query.phoneTypes.handler.ts b/packages/api/router/fieldOpt/query.phoneTypes.handler.ts index 020e7c79ad..4e4f030c97 100644 --- a/packages/api/router/fieldOpt/query.phoneTypes.handler.ts +++ b/packages/api/router/fieldOpt/query.phoneTypes.handler.ts @@ -1,19 +1,14 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' export const phoneTypes = async () => { - try { - const result = await prisma.phoneType.findMany({ - where: { active: true }, - select: { - id: true, - tsKey: true, - tsNs: true, - }, - orderBy: { type: 'asc' }, - }) - return result - } catch (error) { - handleError(error) - } + const result = await prisma.phoneType.findMany({ + where: { active: true }, + select: { + id: true, + tsKey: true, + tsNs: true, + }, + orderBy: { type: 'asc' }, + }) + return result } diff --git a/packages/api/router/fieldOpt/query.userTitle.handler.ts b/packages/api/router/fieldOpt/query.userTitle.handler.ts index 367f2babc8..0a72ea0878 100644 --- a/packages/api/router/fieldOpt/query.userTitle.handler.ts +++ b/packages/api/router/fieldOpt/query.userTitle.handler.ts @@ -1,14 +1,9 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' export const userTitle = async () => { - try { - const results = await prisma.userTitle.findMany({ - where: { searchable: true }, - select: { id: true, title: true }, - }) - return results - } catch (error) { - handleError(error) - } + const results = await prisma.userTitle.findMany({ + where: { searchable: true }, + select: { id: true, title: true }, + }) + return results } diff --git a/packages/api/router/geo/query.autocomplete.handler.ts b/packages/api/router/geo/query.autocomplete.handler.ts index d43de5fd47..28a98ab2f8 100644 --- a/packages/api/router/geo/query.autocomplete.handler.ts +++ b/packages/api/router/geo/query.autocomplete.handler.ts @@ -5,7 +5,6 @@ import { } from '@googlemaps/google-maps-services-js' import { googleMapsApi } from '~api/google' -import { handleError } from '~api/lib/errorHandler' import { googleAPIResponseHandler } from '~api/lib/googleHandler' import { autocompleteResponse } from '~api/schemas/thirdParty/googleGeo' import { type TRPCHandlerParams } from '~api/types/handler' @@ -13,31 +12,27 @@ import { type TRPCHandlerParams } from '~api/types/handler' import { type TAutocompleteSchema } from './query.autocomplete.schema' export const autocomplete = async ({ input }: TRPCHandlerParams) => { - try { - const types = input.cityOnly - ? ['(cities)'] - : input.fullAddress - ? ['address'] - : ([ - 'administrative_area_level_2', - 'administrative_area_level_3', - 'neighborhood', - 'locality', - 'postal_code', - ] as unknown as PlaceAutocompleteType) + const types = input.cityOnly + ? ['(cities)'] + : input.fullAddress + ? ['address'] + : ([ + 'administrative_area_level_2', + 'administrative_area_level_3', + 'neighborhood', + 'locality', + 'postal_code', + ] as unknown as PlaceAutocompleteType) - const { data } = await googleMapsApi.placeAutocomplete({ - params: { - key: process.env.GOOGLE_PLACES_API_KEY as string, - input: input.search, - language: input.locale, - types, - locationbias: 'ipbias', - }, - } as PlaceAutocompleteRequest) - const parsedData = autocompleteResponse.parse(data) - return googleAPIResponseHandler(parsedData, data) - } catch (error) { - handleError(error) - } + const { data } = await googleMapsApi.placeAutocomplete({ + params: { + key: process.env.GOOGLE_PLACES_API_KEY as string, + input: input.search, + language: input.locale, + types, + locationbias: 'ipbias', + }, + } as PlaceAutocompleteRequest) + const parsedData = autocompleteResponse.parse(data) + return googleAPIResponseHandler(parsedData, data) } diff --git a/packages/api/router/geo/query.geoByPlaceId.handler.ts b/packages/api/router/geo/query.geoByPlaceId.handler.ts index 1e8f1a5959..3e38c952c6 100644 --- a/packages/api/router/geo/query.geoByPlaceId.handler.ts +++ b/packages/api/router/geo/query.geoByPlaceId.handler.ts @@ -1,6 +1,5 @@ /* eslint-disable node/no-process-env */ import { googleMapsApi } from '~api/google' -import { handleError } from '~api/lib/errorHandler' import { googleAPIResponseHandler } from '~api/lib/googleHandler' import { geocodeResponse } from '~api/schemas/thirdParty/googleGeo' import { type TRPCHandlerParams } from '~api/types/handler' @@ -8,16 +7,12 @@ import { type TRPCHandlerParams } from '~api/types/handler' import { type TGeoByPlaceIdSchema } from './query.geoByPlaceId.schema' export const geoByPlaceId = async ({ input }: TRPCHandlerParams) => { - try { - const { data } = await googleMapsApi.geocode({ - params: { - key: process.env.GOOGLE_PLACES_API_KEY as string, - place_id: input, - }, - }) - const parsedData = geocodeResponse.parse(data) - return googleAPIResponseHandler(parsedData, data) - } catch (error) { - handleError(error) - } + const { data } = await googleMapsApi.geocode({ + params: { + key: process.env.GOOGLE_PLACES_API_KEY as string, + place_id: input, + }, + }) + const parsedData = geocodeResponse.parse(data) + return googleAPIResponseHandler(parsedData, data) } diff --git a/packages/api/router/internalNote/mutation.create.handler.ts b/packages/api/router/internalNote/mutation.create.handler.ts index dde8dd1cc9..1f589a5a6d 100644 --- a/packages/api/router/internalNote/mutation.create.handler.ts +++ b/packages/api/router/internalNote/mutation.create.handler.ts @@ -1,25 +1,20 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateSchema, ZCreateSchema } from './mutation.create.schema' export const create = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const inputData = { - actorId: ctx.session.user.id, - operation: 'CREATE', - data: input, - } - const { internalNote, auditLog } = ZCreateSchema().dataParser.parse(inputData) - - const results = await prisma.$transaction(async (tx) => { - const note = await tx.internalNote.create(internalNote) - const log = await tx.auditLog.create(auditLog) - return { note, log } - }) - return results.note.id - } catch (error) { - handleError(error) + const inputData = { + actorId: ctx.session.user.id, + operation: 'CREATE', + data: input, } + const { internalNote, auditLog } = ZCreateSchema().dataParser.parse(inputData) + + const results = await prisma.$transaction(async (tx) => { + const note = await tx.internalNote.create(internalNote) + const log = await tx.auditLog.create(auditLog) + return { note, log } + }) + return results.note.id } diff --git a/packages/api/router/internalNote/query.byId.handler.ts b/packages/api/router/internalNote/query.byId.handler.ts index 79d96b902f..76ecb01437 100644 --- a/packages/api/router/internalNote/query.byId.handler.ts +++ b/packages/api/router/internalNote/query.byId.handler.ts @@ -1,14 +1,9 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TByIdSchema } from './query.byId.schema' export const byId = async ({ input }: TRPCHandlerParams) => { - try { - const result = await prisma.internalNote.findFirstOrThrow({ where: { id: input } }) - return result - } catch (error) { - handleError(error) - } + const result = await prisma.internalNote.findFirstOrThrow({ where: { id: input } }) + return result } diff --git a/packages/api/router/internalNote/query.getAllForRecord.handler.ts b/packages/api/router/internalNote/query.getAllForRecord.handler.ts index 1f14a9093c..72e0dabdb1 100644 --- a/packages/api/router/internalNote/query.getAllForRecord.handler.ts +++ b/packages/api/router/internalNote/query.getAllForRecord.handler.ts @@ -1,14 +1,9 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetAllForRecordSchema } from './query.getAllForRecord.schema' export const getAllForRecord = async ({ input }: TRPCHandlerParams) => { - try { - const results = prisma.internalNote.findMany({ where: input }) - return results - } catch (error) { - handleError(error) - } + const results = prisma.internalNote.findMany({ where: input }) + return results } diff --git a/packages/api/router/location/mutation.create.handler.ts b/packages/api/router/location/mutation.create.handler.ts index 249f950dbb..eda0140c17 100644 --- a/packages/api/router/location/mutation.create.handler.ts +++ b/packages/api/router/location/mutation.create.handler.ts @@ -1,17 +1,12 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateSchema, ZCreateSchema } from './mutation.create.schema' export const create = async ({ input }: TRPCHandlerParams) => { - try { - const data = ZCreateSchema().dataParser.parse(input) + const data = ZCreateSchema().dataParser.parse(input) - const result = await prisma.orgLocation.create(data) + const result = await prisma.orgLocation.create(data) - return result - } catch (error) { - handleError(error) - } + return result } diff --git a/packages/api/router/location/mutation.update.handler.ts b/packages/api/router/location/mutation.update.handler.ts index 4c306d703a..5ad11f7af7 100644 --- a/packages/api/router/location/mutation.update.handler.ts +++ b/packages/api/router/location/mutation.update.handler.ts @@ -1,37 +1,26 @@ -import { TRPCError } from '@trpc/server' - import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' -import { updateGeo } from '~api/lib/prismaRaw/updateGeo' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TUpdateSchema } from './mutation.update.schema' export const update = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { where, data } = input - const updatedRecord = await prisma.$transaction(async (tx) => { - const current = await tx.orgLocation.findUniqueOrThrow({ where }) - const auditLog = CreateAuditLog({ - actorId: ctx.session!.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const update = await tx.orgLocation.update({ - where, - data: { ...data, auditLogs: auditLog }, - select: { id: true }, - }) - - // if WKT is updated, we need to have the DB update the `geo` column with raw SQL. - if (data.geoWKT) await updateGeo('orgLocation', where.id, tx) - - return update + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgLocation.findUniqueOrThrow({ where }) + const auditLog = CreateAuditLog({ + actorId: ctx.session!.user.id, + operation: 'UPDATE', + from: current, + to: data, }) - return updatedRecord - } catch (error) { - handleError(error) - } + const update = await tx.orgLocation.update({ + where, + data: { ...data, auditLogs: auditLog }, + select: { id: true }, + }) + + return update + }) + return updatedRecord } diff --git a/packages/api/router/location/mutation.update.schema.ts b/packages/api/router/location/mutation.update.schema.ts index 3e11ed5493..785aae99fd 100644 --- a/packages/api/router/location/mutation.update.schema.ts +++ b/packages/api/router/location/mutation.update.schema.ts @@ -10,8 +10,8 @@ export const ZUpdateSchema = z data: z .object({ name: z.string(), - street1: z.string(), - street2: z.string().nullable(), + street1: z.string().nullish(), + street2: z.string().nullish(), city: z.string(), postCode: z.string().nullable(), primary: z.boolean(), diff --git a/packages/api/router/location/query.forLocationCard.handler.ts b/packages/api/router/location/query.forLocationCard.handler.ts index 4112138ad1..a190e22164 100644 --- a/packages/api/router/location/query.forLocationCard.handler.ts +++ b/packages/api/router/location/query.forLocationCard.handler.ts @@ -1,57 +1,52 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' import { type TForLocationCardSchema } from './query.forLocationCard.schema' export const forLocationCard = async ({ input }: TRPCHandlerParams) => { - try { - const result = await prisma.orgLocation.findUniqueOrThrow({ - where: { - id: input, - ...globalWhere.isPublic(), + const result = await prisma.orgLocation.findUniqueOrThrow({ + where: { + id: input, + ...globalWhere.isPublic(), + }, + select: { + id: true, + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + country: { select: { cca2: true } }, + govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, + phones: { + where: { phone: globalWhere.isPublic() }, + select: { phone: { select: { primary: true, number: true, country: { select: { cca2: true } } } } }, }, - select: { - id: true, - name: true, - street1: true, - street2: true, - city: true, - postCode: true, - country: { select: { cca2: true } }, - govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, - phones: { - where: { phone: globalWhere.isPublic() }, - select: { phone: { select: { primary: true, number: true, country: { select: { cca2: true } } } } }, - }, - attributes: { select: { attribute: { select: { tsNs: true, tsKey: true, icon: true } } } }, - services: { - select: { - service: { - select: { - services: { select: { tag: { select: { category: { select: { tsKey: true } } } } } }, - }, + attributes: { select: { attribute: { select: { tsNs: true, tsKey: true, icon: true } } } }, + services: { + select: { + service: { + select: { + services: { select: { tag: { select: { category: { select: { tsKey: true } } } } } }, }, }, }, }, - }) - - const transformed = { - ...result, - country: result.country.cca2, - phones: result.phones.map(({ phone }) => ({ ...phone, country: phone.country.cca2 })), - attributes: result.attributes.map(({ attribute }) => attribute), - services: [ - ...new Set( - result.services.flatMap(({ service }) => service.services.map(({ tag }) => tag.category.tsKey)) - ), - ], - } + }, + }) - return transformed - } catch (error) { - handleError(error) + const transformed = { + ...result, + country: result.country.cca2, + phones: result.phones.map(({ phone }) => ({ ...phone, country: phone.country.cca2 })), + attributes: result.attributes.map(({ attribute }) => attribute), + services: [ + ...new Set( + result.services.flatMap(({ service }) => service.services.map(({ tag }) => tag.category.tsKey)) + ), + ], } + + return transformed } diff --git a/packages/api/router/location/query.forLocationPage.handler.ts b/packages/api/router/location/query.forLocationPage.handler.ts index b6e7890f40..d18ffd6195 100644 --- a/packages/api/router/location/query.forLocationPage.handler.ts +++ b/packages/api/router/location/query.forLocationPage.handler.ts @@ -1,5 +1,5 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' +import { attributes } from '~api/schemas/selects/common' import { globalSelect, globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' @@ -7,34 +7,30 @@ import { type TForLocationPageSchema } from './query.forLocationPage.schema' import { select } from './selects' export const forLocationPage = async ({ input }: TRPCHandlerParams) => { - try { - const location = await prisma.orgLocation.findUniqueOrThrow({ - where: { - id: input.id, - ...globalWhere.isPublic(), + const location = await prisma.orgLocation.findUniqueOrThrow({ + where: { + id: input.id, + ...globalWhere.isPublic(), + }, + select: { + id: true, + primary: true, + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + country: { select: { cca2: true } }, + govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, + longitude: true, + latitude: true, + description: globalSelect.freeText(), + attributes, + reviews: { + where: { visible: true, deleted: false }, + select: { id: true }, }, - select: { - id: true, - primary: true, - name: true, - street1: true, - street2: true, - city: true, - postCode: true, - country: { select: { cca2: true } }, - govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, - longitude: true, - latitude: true, - description: globalSelect.freeText(), - attributes: select.attributes(), - reviews: { - where: { visible: true, deleted: false }, - select: { id: true }, - }, - }, - }) - return location - } catch (error) { - handleError(error) - } + }, + }) + return location } diff --git a/packages/api/router/location/query.forVisitCard.handler.ts b/packages/api/router/location/query.forVisitCard.handler.ts index c3ee7db106..73ce0ad8d9 100644 --- a/packages/api/router/location/query.forVisitCard.handler.ts +++ b/packages/api/router/location/query.forVisitCard.handler.ts @@ -6,34 +6,30 @@ import { type TRPCHandlerParams } from '~api/types/handler' import { type TForVisitCardSchema } from './query.forVisitCard.schema' export const forVisitCard = async ({ input }: TRPCHandlerParams) => { - try { - const result = await prisma.orgLocation.findUniqueOrThrow({ - where: { - ...globalWhere.isPublic(), - id: input, + const result = await prisma.orgLocation.findUniqueOrThrow({ + where: { + ...globalWhere.isPublic(), + id: input, + }, + select: { + id: true, + street1: true, + street2: true, + city: true, + postCode: true, + country: { select: { cca2: true } }, + govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, + attributes: { + where: { attribute: { tsKey: 'additional.offers-remote-services' } }, + select: { attribute: { select: { tsKey: true, icon: true } } }, }, - select: { - id: true, - street1: true, - street2: true, - city: true, - postCode: true, - country: { select: { cca2: true } }, - govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, - attributes: { - where: { attribute: { tsKey: 'additional.offers-remote-services' } }, - select: { attribute: { select: { tsKey: true, icon: true } } }, - }, - }, - }) - const { attributes, ...rest } = result - const transformed = { - ...rest, - remote: attributes.find(({ attribute }) => attribute.tsKey === 'additional.offers-remote-services') - ?.attribute, - } - return transformed - } catch (error) { - handleError(error) + }, + }) + const { attributes, ...rest } = result + const transformed = { + ...rest, + remote: attributes.find(({ attribute }) => attribute.tsKey === 'additional.offers-remote-services') + ?.attribute, } + return transformed } diff --git a/packages/api/router/location/query.getAddress.handler.ts b/packages/api/router/location/query.getAddress.handler.ts index 2854ea13a7..a4fb0efa12 100644 --- a/packages/api/router/location/query.getAddress.handler.ts +++ b/packages/api/router/location/query.getAddress.handler.ts @@ -1,57 +1,52 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetAddressSchema } from './query.getAddress.schema' export const getAddress = async ({ input }: TRPCHandlerParams) => { - try { - const result = await prisma.orgLocation.findUniqueOrThrow({ - where: { id: input }, - select: { - id: true, - name: true, - street1: true, - street2: true, - city: true, - postCode: true, - govDistId: true, - countryId: true, - attributes: { - select: { - attribute: { select: { id: true, tsKey: true, tsNs: true } }, - supplement: { select: { id: true, boolean: true } }, - }, - where: { attribute: { tag: 'wheelchair-accessible' } }, + const result = await prisma.orgLocation.findUniqueOrThrow({ + where: { id: input }, + select: { + id: true, + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + govDistId: true, + countryId: true, + attributes: { + select: { + attribute: { select: { id: true, tsKey: true, tsNs: true } }, + supplement: { select: { id: true, boolean: true } }, }, - latitude: true, - longitude: true, - mailOnly: true, - published: true, - services: { select: { serviceId: true } }, + where: { attribute: { tag: 'wheelchair-accessible' } }, }, - }) - const { id, attributes, services, ...rest } = result + latitude: true, + longitude: true, + mailOnly: true, + published: true, + services: { select: { serviceId: true } }, + }, + }) + const { id, attributes, services, ...rest } = result - const accessibleAttribute = attributes.find(({ supplement }) => Boolean(supplement.length)) - const { id: supplementId, boolean } = accessibleAttribute - ? accessibleAttribute.supplement.find(({ id }) => Boolean(id)) ?? {} - : { id: undefined, boolean: undefined } + const accessibleAttribute = attributes.find(({ supplement }) => Boolean(supplement.length)) + const { id: supplementId, boolean } = accessibleAttribute + ? accessibleAttribute.supplement.find(({ id }) => Boolean(id)) ?? {} + : { id: undefined, boolean: undefined } - const transformedResult = { - id, - data: { - ...rest, - accessible: { - supplementId, - boolean, - }, - services: services.map(({ serviceId }) => serviceId), + const transformedResult = { + id, + data: { + ...rest, + accessible: { + supplementId, + boolean, }, - } - - return transformedResult - } catch (error) { - handleError(error) + services: services.map(({ serviceId }) => serviceId), + }, } + + return transformedResult } diff --git a/packages/api/router/location/query.getById.handler.ts b/packages/api/router/location/query.getById.handler.ts index 86aecb8c5f..ae8a201999 100644 --- a/packages/api/router/location/query.getById.handler.ts +++ b/packages/api/router/location/query.getById.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalSelect, globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' @@ -7,53 +6,49 @@ import { type TGetByIdSchema } from './query.getById.schema' import { select } from './selects' export const getById = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const location = await prisma.orgLocation.findUniqueOrThrow({ - where: { - id: input.id, - ...globalWhere.isPublic(), - }, - select: { - govDist: globalSelect.govDistBasic(), - country: globalSelect.country(), - attributes: { - where: { - attribute: { - active: true, - categories: { - some: { - category: { - active: true, - }, + const location = await prisma.orgLocation.findUniqueOrThrow({ + where: { + id: input.id, + ...globalWhere.isPublic(), + }, + select: { + govDist: globalSelect.govDistBasic(), + country: globalSelect.country(), + attributes: { + where: { + attribute: { + active: true, + categories: { + some: { + category: { + active: true, }, }, }, }, - ...select.attributes(), }, - emails: { where: { email: globalWhere.isPublic() }, ...select.orgEmail() }, - websites: { where: globalWhere.isPublic(), ...globalSelect.orgWebsite() }, - phones: { where: { phone: globalWhere.isPublic() }, ...select.orgPhone() }, - photos: { where: globalWhere.isPublic(), ...globalSelect.orgPhoto() }, - hours: globalSelect.hours(), - reviews: { where: { visible: true, deleted: false }, select: { id: true } }, - services: { where: { service: globalWhere.isPublic() }, ...select.service(ctx) }, - serviceAreas: globalSelect.serviceArea(), - socialMedia: { where: globalWhere.isPublic(), ...globalSelect.socialMedia() }, - description: globalSelect.freeText(), - name: true, - street1: true, - street2: true, - city: true, - postCode: true, - primary: true, - longitude: true, - latitude: true, - id: true, + ...select.attributes(), }, - }) - return location - } catch (error) { - handleError(error) - } + emails: { where: { email: globalWhere.isPublic() }, ...select.orgEmail() }, + websites: { where: globalWhere.isPublic(), ...globalSelect.orgWebsite() }, + phones: { where: { phone: globalWhere.isPublic() }, ...select.orgPhone() }, + photos: { where: globalWhere.isPublic(), ...globalSelect.orgPhoto() }, + hours: globalSelect.hours(), + reviews: { where: { visible: true, deleted: false }, select: { id: true } }, + services: { where: { service: globalWhere.isPublic() }, ...select.service(ctx) }, + serviceAreas: globalSelect.serviceArea(), + socialMedia: { where: globalWhere.isPublic(), ...globalSelect.socialMedia() }, + description: globalSelect.freeText(), + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + primary: true, + longitude: true, + latitude: true, + id: true, + }, + }) + return location } diff --git a/packages/api/router/location/query.getByOrgId.handler.ts b/packages/api/router/location/query.getByOrgId.handler.ts index 3e6790e104..9f94ba429c 100644 --- a/packages/api/router/location/query.getByOrgId.handler.ts +++ b/packages/api/router/location/query.getByOrgId.handler.ts @@ -1,7 +1,6 @@ import { TRPCError } from '@trpc/server' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalSelect, globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' @@ -9,54 +8,50 @@ import { type TGetByOrgIdSchema } from './query.getByOrgId.schema' import { select } from './selects' export const getByOrgId = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const locations = await prisma.orgLocation.findMany({ - where: { - id: input.orgId, - ...globalWhere.isPublic(), - }, - select: { - govDist: globalSelect.govDistBasic(), - country: globalSelect.country(), - attributes: { - where: { - attribute: { - active: true, - categories: { - some: { - category: { - active: true, - }, + const locations = await prisma.orgLocation.findMany({ + where: { + id: input.orgId, + ...globalWhere.isPublic(), + }, + select: { + govDist: globalSelect.govDistBasic(), + country: globalSelect.country(), + attributes: { + where: { + attribute: { + active: true, + categories: { + some: { + category: { + active: true, }, }, }, }, - ...select.attributes(), }, - emails: { where: { email: globalWhere.isPublic() }, ...select.orgEmail() }, - websites: { where: globalWhere.isPublic(), ...globalSelect.orgWebsite() }, - phones: { where: { phone: globalWhere.isPublic() }, ...select.orgPhone() }, - photos: { where: globalWhere.isPublic(), ...globalSelect.orgPhoto() }, - hours: globalSelect.hours(), - reviews: { where: { visible: true, deleted: false }, select: { id: true } }, - services: { where: { service: globalWhere.isPublic() }, ...select.service(ctx) }, - serviceAreas: globalSelect.serviceArea(), - socialMedia: { where: globalWhere.isPublic(), ...globalSelect.socialMedia() }, - description: globalSelect.freeText(), - name: true, - street1: true, - street2: true, - city: true, - postCode: true, - primary: true, - longitude: true, - latitude: true, - id: true, + ...select.attributes(), }, - }) - if (locations.length === 0) throw new TRPCError({ code: 'NOT_FOUND' }) - return locations - } catch (error) { - handleError(error) - } + emails: { where: { email: globalWhere.isPublic() }, ...select.orgEmail() }, + websites: { where: globalWhere.isPublic(), ...globalSelect.orgWebsite() }, + phones: { where: { phone: globalWhere.isPublic() }, ...select.orgPhone() }, + photos: { where: globalWhere.isPublic(), ...globalSelect.orgPhoto() }, + hours: globalSelect.hours(), + reviews: { where: { visible: true, deleted: false }, select: { id: true } }, + services: { where: { service: globalWhere.isPublic() }, ...select.service(ctx) }, + serviceAreas: globalSelect.serviceArea(), + socialMedia: { where: globalWhere.isPublic(), ...globalSelect.socialMedia() }, + description: globalSelect.freeText(), + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + primary: true, + longitude: true, + latitude: true, + id: true, + }, + }) + if (locations.length === 0) throw new TRPCError({ code: 'NOT_FOUND' }) + return locations } diff --git a/packages/api/router/location/query.getNameById.handler.ts b/packages/api/router/location/query.getNameById.handler.ts index f702f5b874..bb42a9c106 100644 --- a/packages/api/router/location/query.getNameById.handler.ts +++ b/packages/api/router/location/query.getNameById.handler.ts @@ -1,17 +1,12 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetNameByIdSchema } from './query.getNameById.schema' export const getNameById = async ({ input }: TRPCHandlerParams) => { - try { - const result = await prisma.orgLocation.findUniqueOrThrow({ - where: { id: input }, - select: { name: true }, - }) - return result - } catch (error) { - handleError(error) - } + const result = await prisma.orgLocation.findUniqueOrThrow({ + where: { id: input }, + select: { name: true }, + }) + return result } diff --git a/packages/api/router/location/query.getNames.handler.ts b/packages/api/router/location/query.getNames.handler.ts index c9cdb6ef45..4b5fcfec9a 100644 --- a/packages/api/router/location/query.getNames.handler.ts +++ b/packages/api/router/location/query.getNames.handler.ts @@ -1,23 +1,18 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetNamesSchema } from './query.getNames.schema' export const getNames = async ({ input }: TRPCHandlerParams) => { - try { - const results = await prisma.orgLocation.findMany({ - where: { - organization: { id: input.organizationId }, - }, - select: { - id: true, - name: true, - }, - }) + const results = await prisma.orgLocation.findMany({ + where: { + organization: { id: input.organizationId }, + }, + select: { + id: true, + name: true, + }, + }) - return results - } catch (error) { - handleError(error) - } + return results } diff --git a/packages/api/router/orgEmail/mutation.create.handler.ts b/packages/api/router/orgEmail/mutation.create.handler.ts index d16d945a37..699910dfd3 100644 --- a/packages/api/router/orgEmail/mutation.create.handler.ts +++ b/packages/api/router/orgEmail/mutation.create.handler.ts @@ -1,22 +1,17 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateSchema } from './mutation.create.schema' export const create = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) - const newEmail = await prisma.orgEmail.create({ - data: { - ...input, - auditLogs, - }, - select: { id: true }, - }) - return newEmail - } catch (error) { - handleError(error) - } + const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) + const newEmail = await prisma.orgEmail.create({ + data: { + ...input, + auditLogs, + }, + select: { id: true }, + }) + return newEmail } diff --git a/packages/api/router/orgEmail/mutation.update.handler.ts b/packages/api/router/orgEmail/mutation.update.handler.ts index e648e59fa6..456dbbda11 100644 --- a/packages/api/router/orgEmail/mutation.update.handler.ts +++ b/packages/api/router/orgEmail/mutation.update.handler.ts @@ -1,32 +1,27 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TUpdateSchema } from './mutation.update.schema' export const update = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { where, data } = input - const updatedRecord = await prisma.$transaction(async (tx) => { - const current = await tx.orgEmail.findUniqueOrThrow({ where }) - const auditLogs = CreateAuditLog({ - actorId: ctx.session.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const updated = await tx.orgEmail.update({ - where, - data: { - ...data, - auditLogs, - }, - }) - return updated + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgEmail.findUniqueOrThrow({ where }) + const auditLogs = CreateAuditLog({ + actorId: ctx.session.user.id, + operation: 'UPDATE', + from: current, + to: data, }) - return updatedRecord - } catch (error) { - handleError(error) - } + const updated = await tx.orgEmail.update({ + where, + data: { + ...data, + auditLogs, + }, + }) + return updated + }) + return updatedRecord } diff --git a/packages/api/router/orgEmail/mutation.upsertMany.handler.ts b/packages/api/router/orgEmail/mutation.upsertMany.handler.ts index 24647a6d82..d8a1aac4d8 100644 --- a/packages/api/router/orgEmail/mutation.upsertMany.handler.ts +++ b/packages/api/router/orgEmail/mutation.upsertMany.handler.ts @@ -1,7 +1,6 @@ import compact from 'just-compact' import { generateNestedFreeText, prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { connectOneId, @@ -14,72 +13,68 @@ import { type TRPCHandlerParams } from '~api/types/handler' import { type TUpsertManySchema } from './mutation.upsertMany.schema' export const upsertMany = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { orgId, data } = input + const { orgId, data } = input - const existing = await prisma.orgEmail.findMany({ - where: { - id: { in: compact(data.map(({ id }) => id)) }, - }, - include: { services: true, locations: true }, - }) - const upserts = await prisma.$transaction( - data.map( - ({ title, services: servicesArr, locations: locationsArr, description, id: passedId, ...record }) => { - const before = passedId ? existing.find(({ id }) => id === passedId) : undefined - const servicesBefore = before?.services.map(({ serviceId }) => ({ serviceId })) ?? [] - const locationsBefore = before?.locations.map(({ orgLocationId }) => ({ orgLocationId })) ?? [] - const auditLogs = CreateAuditLog({ - actorId: ctx.actorId, - operation: before ? 'UPDATE' : 'CREATE', - from: before, - to: record, - }) - const id = passedId ?? ctx.generateId('orgEmail') + const existing = await prisma.orgEmail.findMany({ + where: { + id: { in: compact(data.map(({ id }) => id)) }, + }, + include: { services: true, locations: true }, + }) + const upserts = await prisma.$transaction( + data.map( + ({ title, services: servicesArr, locations: locationsArr, description, id: passedId, ...record }) => { + const before = passedId ? existing.find(({ id }) => id === passedId) : undefined + const servicesBefore = before?.services.map(({ serviceId }) => ({ serviceId })) ?? [] + const locationsBefore = before?.locations.map(({ orgLocationId }) => ({ orgLocationId })) ?? [] + const auditLogs = CreateAuditLog({ + actorId: ctx.actorId, + operation: before ? 'UPDATE' : 'CREATE', + from: before, + to: record, + }) + const id = passedId ?? ctx.generateId('orgEmail') - const services = servicesArr.map((serviceId) => ({ serviceId })) - const locations = locationsArr.map((orgLocationId) => ({ orgLocationId })) + const services = servicesArr.map((serviceId) => ({ serviceId })) + const locations = locationsArr.map((orgLocationId) => ({ orgLocationId })) - return prisma.orgEmail.upsert({ - where: { id }, - create: { - id, - ...record, - title: connectOneId(title), - services: createManyOptional(services), - locations: createManyOptional(locations), - auditLogs, - description: description - ? generateNestedFreeText({ orgId, text: description, type: 'emailDesc', itemId: id }) - : undefined, - }, - update: { - id, - ...record, - title: connectOrDisconnectId(title), - services: diffConnectionsMtoN(services, servicesBefore, 'serviceId'), - locations: diffConnectionsMtoN(locations, locationsBefore, 'orgLocationId'), - description: description - ? { - upsert: { - ...generateNestedFreeText({ - orgId, - text: description, - type: 'emailDesc', - itemId: id, - }), - update: { tsKey: { update: { text: description } } }, - }, - } - : undefined, - auditLogs, - }, - }) - } - ) + return prisma.orgEmail.upsert({ + where: { id }, + create: { + id, + ...record, + title: connectOneId(title), + services: createManyOptional(services), + locations: createManyOptional(locations), + auditLogs, + description: description + ? generateNestedFreeText({ orgId, text: description, type: 'emailDesc', itemId: id }) + : undefined, + }, + update: { + id, + ...record, + title: connectOrDisconnectId(title), + services: diffConnectionsMtoN(services, servicesBefore, 'serviceId'), + locations: diffConnectionsMtoN(locations, locationsBefore, 'orgLocationId'), + description: description + ? { + upsert: { + ...generateNestedFreeText({ + orgId, + text: description, + type: 'emailDesc', + itemId: id, + }), + update: { tsKey: { update: { text: description } } }, + }, + } + : undefined, + auditLogs, + }, + }) + } ) - return upserts - } catch (error) { - handleError(error) - } + ) + return upserts } diff --git a/packages/api/router/orgEmail/query.forContactInfo.handler.ts b/packages/api/router/orgEmail/query.forContactInfo.handler.ts index f4cc684783..8df9276ffc 100644 --- a/packages/api/router/orgEmail/query.forContactInfo.handler.ts +++ b/packages/api/router/orgEmail/query.forContactInfo.handler.ts @@ -1,56 +1,51 @@ import { isIdFor, prisma, type Prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' import { type TForContactInfoSchema } from './query.forContactInfo.schema' export const forContactInfo = async ({ input }: TRPCHandlerParams) => { - try { - const whereId = (): Prisma.OrgEmailWhereInput => { - switch (true) { - case isIdFor('organization', input.parentId): { - return { - organization: { some: { organization: { id: input.parentId, ...globalWhere.isPublic() } } }, - } - } - case isIdFor('orgLocation', input.parentId): { - return { locations: { some: { location: { id: input.parentId, ...globalWhere.isPublic() } } } } - } - case isIdFor('orgService', input.parentId): { - return { services: { some: { service: { id: input.parentId, ...globalWhere.isPublic() } } } } - } - default: { - return {} + const whereId = (): Prisma.OrgEmailWhereInput => { + switch (true) { + case isIdFor('organization', input.parentId): { + return { + organization: { some: { organization: { id: input.parentId, ...globalWhere.isPublic() } } }, } } + case isIdFor('orgLocation', input.parentId): { + return { locations: { some: { location: { id: input.parentId, ...globalWhere.isPublic() } } } } + } + case isIdFor('orgService', input.parentId): { + return { services: { some: { service: { id: input.parentId, ...globalWhere.isPublic() } } } } + } + default: { + return {} + } } - - const result = await prisma.orgEmail.findMany({ - where: { - ...globalWhere.isPublic(), - ...whereId(), - ...(input.locationOnly !== undefined ? { locationOnly: input.locationOnly } : {}), - ...(input.serviceOnly !== undefined ? { serviceOnly: input.serviceOnly } : {}), - }, - select: { - id: true, - email: true, - primary: true, - title: { select: { key: { select: { key: true } } } }, - description: { select: { tsKey: { select: { text: true, key: true } } } }, - locationOnly: true, - serviceOnly: true, - }, - orderBy: { primary: 'desc' }, - }) - const transformed = result.map(({ description, title, ...record }) => ({ - ...record, - title: title ? { key: title?.key.key } : null, - description: description ? { key: description?.tsKey.key, defaultText: description?.tsKey.text } : null, - })) - return transformed - } catch (error) { - handleError(error) } + + const result = await prisma.orgEmail.findMany({ + where: { + ...globalWhere.isPublic(), + ...whereId(), + ...(input.locationOnly !== undefined ? { locationOnly: input.locationOnly } : {}), + ...(input.serviceOnly !== undefined ? { serviceOnly: input.serviceOnly } : {}), + }, + select: { + id: true, + email: true, + primary: true, + title: { select: { key: { select: { key: true } } } }, + description: { select: { tsKey: { select: { text: true, key: true } } } }, + locationOnly: true, + serviceOnly: true, + }, + orderBy: { primary: 'desc' }, + }) + const transformed = result.map(({ description, title, ...record }) => ({ + ...record, + title: title ? { key: title?.key.key } : null, + description: description ? { key: description?.tsKey.key, defaultText: description?.tsKey.text } : null, + })) + return transformed } diff --git a/packages/api/router/orgEmail/query.get.handler.ts b/packages/api/router/orgEmail/query.get.handler.ts index a1f3961d42..fd99b35bc2 100644 --- a/packages/api/router/orgEmail/query.get.handler.ts +++ b/packages/api/router/orgEmail/query.get.handler.ts @@ -1,61 +1,56 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetSchema } from './query.get.schema' export const get = async ({ input }: TRPCHandlerParams) => { - try { - const { id, orgLocationId, organizationId, serviceId } = input + const { id, orgLocationId, organizationId, serviceId } = input - const result = await prisma.orgEmail.findMany({ - where: { - id, - ...(organizationId ? { organization: { some: { organizationId } } } : {}), - ...(orgLocationId ? { locations: { some: { orgLocationId } } } : {}), - ...(serviceId ? { services: { some: { serviceId } } } : {}), - }, - select: { - id: true, - email: true, - firstName: true, - lastName: true, - titleId: true, - primary: true, - published: true, - deleted: true, - description: { select: { tsKey: { select: { text: true } } } }, - locations: { select: { location: { select: { id: true, name: true } } } }, - organization: { select: { organization: { select: { id: true, name: true, slug: true } } } }, - services: { - select: { - service: { - select: { - id: true, - serviceName: { select: { tsKey: { select: { text: true } } } }, - }, + const result = await prisma.orgEmail.findMany({ + where: { + id, + ...(organizationId ? { organization: { some: { organizationId } } } : {}), + ...(orgLocationId ? { locations: { some: { orgLocationId } } } : {}), + ...(serviceId ? { services: { some: { serviceId } } } : {}), + }, + select: { + id: true, + email: true, + firstName: true, + lastName: true, + titleId: true, + primary: true, + published: true, + deleted: true, + description: { select: { tsKey: { select: { text: true } } } }, + locations: { select: { location: { select: { id: true, name: true } } } }, + organization: { select: { organization: { select: { id: true, name: true, slug: true } } } }, + services: { + select: { + service: { + select: { + id: true, + serviceName: { select: { tsKey: { select: { text: true } } } }, }, }, }, }, - orderBy: [{ published: 'desc' }, { deleted: 'desc' }], - }) + }, + orderBy: [{ published: 'desc' }, { deleted: 'desc' }], + }) - const transformedResult = result.map( - ({ description, locations, organization, titleId, services, ...record }) => ({ - ...record, - description: description?.tsKey.text, - locations: locations.map(({ location }) => ({ ...location })), - organization: organization.map(({ organization }) => ({ ...organization })), - title: titleId, - services: services.map(({ service }) => ({ - id: service.id, - serviceName: service.serviceName?.tsKey.text, - })), - }) - ) - return transformedResult - } catch (error) { - handleError(error) - } + const transformedResult = result.map( + ({ description, locations, organization, titleId, services, ...record }) => ({ + ...record, + description: description?.tsKey.text, + locations: locations.map(({ location }) => ({ ...location })), + organization: organization.map(({ organization }) => ({ ...organization })), + title: titleId, + services: services.map(({ service }) => ({ + id: service.id, + serviceName: service.serviceName?.tsKey.text, + })), + }) + ) + return transformedResult } diff --git a/packages/api/router/orgEmail/query.get.schema.ts b/packages/api/router/orgEmail/query.get.schema.ts index 7b6f953d88..bda427bd60 100644 --- a/packages/api/router/orgEmail/query.get.schema.ts +++ b/packages/api/router/orgEmail/query.get.schema.ts @@ -5,27 +5,27 @@ import { prefixedId } from '~api/schemas/idPrefix' export const ZGetSchema = z.union([ z.object({ id: prefixedId('orgEmail'), - orgLocationId: z.never(), - serviceId: z.never(), - organizationId: z.never(), + orgLocationId: z.undefined().optional(), + serviceId: z.undefined().optional(), + organizationId: z.undefined().optional(), }), z.object({ orgLocationId: prefixedId('orgLocation'), - serviceId: z.never(), - organizationId: z.never(), - id: z.never(), + serviceId: z.undefined().optional(), + organizationId: z.undefined().optional(), + id: z.undefined().optional(), }), z.object({ serviceId: prefixedId('orgService'), - organizationId: z.never(), - id: z.never(), - orgLocationId: z.never(), + organizationId: z.undefined().optional(), + id: z.undefined().optional(), + orgLocationId: z.undefined().optional(), }), z.object({ organizationId: prefixedId('organization'), - id: z.never(), - orgLocationId: z.never(), - serviceId: z.never(), + id: z.undefined().optional(), + orgLocationId: z.undefined().optional(), + serviceId: z.undefined().optional(), }), ]) export type TGetSchema = z.infer diff --git a/packages/api/router/orgHours/mutation.create.handler.ts b/packages/api/router/orgHours/mutation.create.handler.ts index cfcb8ed88f..2ccbd126d0 100644 --- a/packages/api/router/orgHours/mutation.create.handler.ts +++ b/packages/api/router/orgHours/mutation.create.handler.ts @@ -1,22 +1,17 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateSchema } from './mutation.create.schema' export const create = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) - const newRecord = await prisma.orgHours.create({ - data: { - ...input, - auditLogs, - }, - select: { id: true }, - }) - return newRecord - } catch (error) { - handleError(error) - } + const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) + const newRecord = await prisma.orgHours.create({ + data: { + ...input, + auditLogs, + }, + select: { id: true }, + }) + return newRecord } diff --git a/packages/api/router/orgHours/mutation.createMany.handler.ts b/packages/api/router/orgHours/mutation.createMany.handler.ts index 6016bbaa23..7aa2fb0443 100644 --- a/packages/api/router/orgHours/mutation.createMany.handler.ts +++ b/packages/api/router/orgHours/mutation.createMany.handler.ts @@ -1,25 +1,20 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateManySchema, ZCreateManySchema } from './mutation.createMany.schema' export const createMany = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const inputData = { - actorId: ctx.session.user.id, - operation: 'CREATE', - data: input, - } - const { orgHours, auditLogs } = ZCreateManySchema().dataParser.parse(inputData) - const results = await prisma.$transaction(async (tx) => { - const hours = await tx.orgHours.createMany(orgHours) - const logs = await tx.auditLog.createMany(auditLogs) - - return { orgHours: hours.count, auditLogs: logs.count, balanced: hours.count === logs.count } - }) - return results - } catch (error) { - handleError(error) + const inputData = { + actorId: ctx.session.user.id, + operation: 'CREATE', + data: input, } + const { orgHours, auditLogs } = ZCreateManySchema().dataParser.parse(inputData) + const results = await prisma.$transaction(async (tx) => { + const hours = await tx.orgHours.createMany(orgHours) + const logs = await tx.auditLog.createMany(auditLogs) + + return { orgHours: hours.count, auditLogs: logs.count, balanced: hours.count === logs.count } + }) + return results } diff --git a/packages/api/router/orgHours/mutation.update.handler.ts b/packages/api/router/orgHours/mutation.update.handler.ts index d3698cf0df..99c37ccbe3 100644 --- a/packages/api/router/orgHours/mutation.update.handler.ts +++ b/packages/api/router/orgHours/mutation.update.handler.ts @@ -1,32 +1,27 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TUpdateSchema } from './mutation.update.schema' export const update = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { where, data } = input - const updatedRecord = await prisma.$transaction(async (tx) => { - const current = await tx.orgHours.findUniqueOrThrow({ where }) - const auditLogs = CreateAuditLog({ - actorId: ctx.session.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const updated = await tx.orgHours.update({ - where, - data: { - ...data, - auditLogs, - }, - }) - return updated + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgHours.findUniqueOrThrow({ where }) + const auditLogs = CreateAuditLog({ + actorId: ctx.session.user.id, + operation: 'UPDATE', + from: current, + to: data, }) - return updatedRecord - } catch (error) { - handleError(error) - } + const updated = await tx.orgHours.update({ + where, + data: { + ...data, + auditLogs, + }, + }) + return updated + }) + return updatedRecord } diff --git a/packages/api/router/orgHours/query.forHoursDisplay.handler.ts b/packages/api/router/orgHours/query.forHoursDisplay.handler.ts index b1d143428d..110e71ddbe 100644 --- a/packages/api/router/orgHours/query.forHoursDisplay.handler.ts +++ b/packages/api/router/orgHours/query.forHoursDisplay.handler.ts @@ -1,38 +1,33 @@ import { isIdFor, prisma, type Prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' import { type TForHoursDisplaySchema } from './query.forHoursDisplay.schema' -export const forHoursDisplay = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const whereId = (): Prisma.OrgHoursWhereInput => { - switch (true) { - case isIdFor('organization', input): { - return { organization: { id: input, ...globalWhere.isPublic() } } - } - case isIdFor('orgLocation', input): { - return { orgLocation: { id: input, ...globalWhere.isPublic() } } - } - case isIdFor('orgService', input): { - return { orgService: { id: input, ...globalWhere.isPublic() } } - } - default: { - return {} - } +export const forHoursDisplay = async ({ input }: TRPCHandlerParams) => { + const whereId = (): Prisma.OrgHoursWhereInput => { + switch (true) { + case isIdFor('organization', input): { + return { organization: { id: input, ...globalWhere.isPublic() } } + } + case isIdFor('orgLocation', input): { + return { orgLocation: { id: input, ...globalWhere.isPublic() } } + } + case isIdFor('orgService', input): { + return { orgService: { id: input, ...globalWhere.isPublic() } } + } + default: { + return {} } } - - const result = await prisma.orgHours.findMany({ - where: { - ...whereId(), - }, - select: { id: true, dayIndex: true, start: true, end: true, closed: true, tz: true }, - orderBy: [{ dayIndex: 'asc' }, { start: 'asc' }], - }) - return result - } catch (error) { - handleError(error) } + + const result = await prisma.orgHours.findMany({ + where: { + ...whereId(), + }, + select: { id: true, dayIndex: true, start: true, end: true, closed: true, tz: true }, + orderBy: [{ dayIndex: 'asc' }, { start: 'asc' }], + }) + return result } diff --git a/packages/api/router/orgHours/query.forHoursDrawer.handler.ts b/packages/api/router/orgHours/query.forHoursDrawer.handler.ts index 8cb8bee7e8..667092692d 100644 --- a/packages/api/router/orgHours/query.forHoursDrawer.handler.ts +++ b/packages/api/router/orgHours/query.forHoursDrawer.handler.ts @@ -1,55 +1,50 @@ import { DateTime } from 'luxon' import { isIdFor, prisma, type Prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' import { type TForHoursDrawerSchema } from './query.forHoursDrawer.schema' export const forHoursDrawer = async ({ input }: TRPCHandlerParams) => { - try { - const whereId = (): Prisma.OrgHoursWhereInput => { - switch (true) { - case isIdFor('organization', input): { - return { organization: { id: input, ...globalWhere.isPublic() } } - } - case isIdFor('orgLocation', input): { - return { orgLocation: { id: input, ...globalWhere.isPublic() } } - } - case isIdFor('orgService', input): { - return { orgService: { id: input, ...globalWhere.isPublic() } } - } - default: { - return {} - } + const whereId = (): Prisma.OrgHoursWhereInput => { + switch (true) { + case isIdFor('organization', input): { + return { organization: { id: input, ...globalWhere.isPublic() } } } - } - - const result = await prisma.orgHours.findMany({ - where: { - ...whereId(), - }, - select: { id: true, dayIndex: true, start: true, end: true, closed: true, tz: true }, - orderBy: [{ dayIndex: 'asc' }, { start: 'asc' }], - }) - const transformedResult = result.map(({ start, end, ...rest }) => { - return { - start: DateTime.fromJSDate(start, { zone: rest.tz ?? 'America/New_York' }).toISOTime({ - suppressMilliseconds: true, - suppressSeconds: true, - includeOffset: false, - }), - end: DateTime.fromJSDate(end, { zone: rest.tz ?? 'America/New_York' }).toISOTime({ - suppressMilliseconds: true, - suppressSeconds: true, - includeOffset: false, - }), - ...rest, + case isIdFor('orgLocation', input): { + return { orgLocation: { id: input, ...globalWhere.isPublic() } } + } + case isIdFor('orgService', input): { + return { orgService: { id: input, ...globalWhere.isPublic() } } + } + default: { + return {} } - }) - return transformedResult - } catch (error) { - handleError(error) + } } + + const result = await prisma.orgHours.findMany({ + where: { + ...whereId(), + }, + select: { id: true, dayIndex: true, start: true, end: true, closed: true, tz: true }, + orderBy: [{ dayIndex: 'asc' }, { start: 'asc' }], + }) + const transformedResult = result.map(({ start, end, ...rest }) => { + return { + start: DateTime.fromJSDate(start, { zone: rest.tz ?? 'America/New_York' }).toISOTime({ + suppressMilliseconds: true, + suppressSeconds: true, + includeOffset: false, + }), + end: DateTime.fromJSDate(end, { zone: rest.tz ?? 'America/New_York' }).toISOTime({ + suppressMilliseconds: true, + suppressSeconds: true, + includeOffset: false, + }), + ...rest, + } + }) + return transformedResult } diff --git a/packages/api/router/orgHours/query.getTz.handler.ts b/packages/api/router/orgHours/query.getTz.handler.ts index f6cb6917d4..628d40afd2 100644 --- a/packages/api/router/orgHours/query.getTz.handler.ts +++ b/packages/api/router/orgHours/query.getTz.handler.ts @@ -1,19 +1,13 @@ import { TRPCError } from '@trpc/server' import { find } from 'geo-tz' -import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetTzSchema } from './query.getTz.schema' export const getTz = async ({ input }: TRPCHandlerParams) => { - try { - const { lat, lon } = input - const result = find(lat, lon).at(0) - if (!result) throw new TRPCError({ code: 'NOT_FOUND' }) - return result - } catch (error) { - handleError(error) - } + const { lat, lon } = input + const result = find(lat, lon).at(0) + if (!result) throw new TRPCError({ code: 'NOT_FOUND' }) + return result } diff --git a/packages/api/router/orgPhone/mutation.create.handler.ts b/packages/api/router/orgPhone/mutation.create.handler.ts index 9e1730c377..f47f825832 100644 --- a/packages/api/router/orgPhone/mutation.create.handler.ts +++ b/packages/api/router/orgPhone/mutation.create.handler.ts @@ -1,22 +1,17 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateSchema } from './mutation.create.schema' export const create = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) - const newPhone = await prisma.orgPhone.create({ - data: { - ...input, - auditLogs, - }, - select: { id: true }, - }) - return newPhone - } catch (error) { - handleError(error) - } + const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) + const newPhone = await prisma.orgPhone.create({ + data: { + ...input, + auditLogs, + }, + select: { id: true }, + }) + return newPhone } diff --git a/packages/api/router/orgPhone/mutation.update.handler.ts b/packages/api/router/orgPhone/mutation.update.handler.ts index df8f0d9976..ec6e18ef0e 100644 --- a/packages/api/router/orgPhone/mutation.update.handler.ts +++ b/packages/api/router/orgPhone/mutation.update.handler.ts @@ -1,32 +1,27 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TUpdateSchema } from './mutation.update.schema' export const update = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { where, data } = input - const updatedRecord = await prisma.$transaction(async (tx) => { - const current = await tx.orgPhone.findUniqueOrThrow({ where }) - const auditLogs = CreateAuditLog({ - actorId: ctx.session.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const updated = await tx.orgPhone.update({ - where, - data: { - ...data, - auditLogs, - }, - }) - return updated + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgPhone.findUniqueOrThrow({ where }) + const auditLogs = CreateAuditLog({ + actorId: ctx.session.user.id, + operation: 'UPDATE', + from: current, + to: data, }) - return updatedRecord - } catch (error) { - handleError(error) - } + const updated = await tx.orgPhone.update({ + where, + data: { + ...data, + auditLogs, + }, + }) + return updated + }) + return updatedRecord } diff --git a/packages/api/router/orgPhone/mutation.upsertMany.handler.ts b/packages/api/router/orgPhone/mutation.upsertMany.handler.ts index 31385a2a68..7f3ebdd8a2 100644 --- a/packages/api/router/orgPhone/mutation.upsertMany.handler.ts +++ b/packages/api/router/orgPhone/mutation.upsertMany.handler.ts @@ -1,7 +1,6 @@ import compact from 'just-compact' import { generateNestedFreeText, prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { connectOneId, @@ -15,82 +14,78 @@ import { type TRPCHandlerParams } from '~api/types/handler' import { type TUpsertManySchema } from './mutation.upsertMany.schema' export const upsertMany = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { orgId, data } = input + const { orgId, data } = input - const existing = await prisma.orgPhone.findMany({ - where: { - id: { in: compact(data.map(({ id }) => id)) }, - }, - include: { services: true, locations: true }, - }) - const upserts = await prisma.$transaction( - data.map( - ({ - phoneType, - country, - services: servicesArr, - locations: locationsArr, - description, - id: passedId, - ...record - }) => { - const before = passedId ? existing.find(({ id }) => id === passedId) : undefined - const servicesBefore = before?.services.map(({ serviceId }) => ({ serviceId })) ?? [] - const locationsBefore = before?.locations.map(({ orgLocationId }) => ({ orgLocationId })) ?? [] - const auditLogs = CreateAuditLog({ - actorId: ctx.actorId, - operation: before ? 'UPDATE' : 'CREATE', - from: before, - to: record, - }) - const id = passedId ?? ctx.generateId('orgPhone') + const existing = await prisma.orgPhone.findMany({ + where: { + id: { in: compact(data.map(({ id }) => id)) }, + }, + include: { services: true, locations: true }, + }) + const upserts = await prisma.$transaction( + data.map( + ({ + phoneType, + country, + services: servicesArr, + locations: locationsArr, + description, + id: passedId, + ...record + }) => { + const before = passedId ? existing.find(({ id }) => id === passedId) : undefined + const servicesBefore = before?.services.map(({ serviceId }) => ({ serviceId })) ?? [] + const locationsBefore = before?.locations.map(({ orgLocationId }) => ({ orgLocationId })) ?? [] + const auditLogs = CreateAuditLog({ + actorId: ctx.actorId, + operation: before ? 'UPDATE' : 'CREATE', + from: before, + to: record, + }) + const id = passedId ?? ctx.generateId('orgPhone') - const services = servicesArr.map((serviceId) => ({ serviceId })) - const locations = locationsArr.map((orgLocationId) => ({ orgLocationId })) + const services = servicesArr.map((serviceId) => ({ serviceId })) + const locations = locationsArr.map((orgLocationId) => ({ orgLocationId })) - return prisma.orgPhone.upsert({ - where: { id }, - create: { - id, - ...record, - country: connectOneIdRequired(country.id), - phoneType: connectOneId(phoneType), - services: createManyOptional(services), - locations: createManyOptional(locations), - auditLogs, - description: description - ? generateNestedFreeText({ orgId, text: description, type: 'phoneDesc', itemId: id }) - : undefined, - }, - update: { - id, - ...record, - country: connectOneIdRequired(country.id), - phoneType: connectOrDisconnectId(phoneType), - services: diffConnectionsMtoN(services, servicesBefore, 'serviceId'), - locations: diffConnectionsMtoN(locations, locationsBefore, 'orgLocationId'), - description: description - ? { - upsert: { - ...generateNestedFreeText({ - orgId, - text: description, - type: 'phoneDesc', - itemId: id, - }), - update: { tsKey: { update: { text: description } } }, - }, - } - : undefined, - auditLogs, - }, - }) - } - ) + return prisma.orgPhone.upsert({ + where: { id }, + create: { + id, + ...record, + country: connectOneIdRequired(country.id), + phoneType: connectOneId(phoneType), + services: createManyOptional(services), + locations: createManyOptional(locations), + auditLogs, + description: description + ? generateNestedFreeText({ orgId, text: description, type: 'phoneDesc', itemId: id }) + : undefined, + }, + update: { + id, + ...record, + country: connectOneIdRequired(country.id), + phoneType: connectOrDisconnectId(phoneType), + services: diffConnectionsMtoN(services, servicesBefore, 'serviceId'), + locations: diffConnectionsMtoN(locations, locationsBefore, 'orgLocationId'), + description: description + ? { + upsert: { + ...generateNestedFreeText({ + orgId, + text: description, + type: 'phoneDesc', + itemId: id, + }), + update: { tsKey: { update: { text: description } } }, + }, + } + : undefined, + auditLogs, + }, + }) + } ) - return upserts - } catch (error) { - handleError(error) - } + ) + return upserts } diff --git a/packages/api/router/orgPhone/query.forContactInfo.handler.ts b/packages/api/router/orgPhone/query.forContactInfo.handler.ts index e980ecdb20..954fbc4d53 100644 --- a/packages/api/router/orgPhone/query.forContactInfo.handler.ts +++ b/packages/api/router/orgPhone/query.forContactInfo.handler.ts @@ -1,55 +1,50 @@ import { isIdFor, type Prisma, prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' import { type TForContactInfoSchema } from './query.forContactInfo.schema' export const forContactInfo = async ({ input }: TRPCHandlerParams) => { - try { - const whereId = (): Prisma.OrgPhoneWhereInput => { - switch (true) { - case isIdFor('organization', input.parentId): { - return { organization: { organization: { id: input.parentId, ...globalWhere.isPublic() } } } - } - case isIdFor('orgLocation', input.parentId): { - return { locations: { some: { location: { id: input.parentId, ...globalWhere.isPublic() } } } } - } - case isIdFor('orgService', input.parentId): { - return { services: { some: { service: { id: input.parentId, ...globalWhere.isPublic() } } } } - } - default: { - return {} - } + const whereId = (): Prisma.OrgPhoneWhereInput => { + switch (true) { + case isIdFor('organization', input.parentId): { + return { organization: { organization: { id: input.parentId, ...globalWhere.isPublic() } } } + } + case isIdFor('orgLocation', input.parentId): { + return { locations: { some: { location: { id: input.parentId, ...globalWhere.isPublic() } } } } + } + case isIdFor('orgService', input.parentId): { + return { services: { some: { service: { id: input.parentId, ...globalWhere.isPublic() } } } } + } + default: { + return {} } } - - const result = await prisma.orgPhone.findMany({ - where: { - ...globalWhere.isPublic(), - ...whereId(), - ...(input.locationOnly !== undefined ? { locationOnly: input.locationOnly } : {}), - }, - select: { - id: true, - number: true, - ext: true, - country: { select: { cca2: true } }, - primary: true, - description: { select: { tsKey: { select: { text: true, key: true } } } }, - phoneType: { select: { key: { select: { text: true, key: true } } } }, - locationOnly: true, - }, - orderBy: { primary: 'desc' }, - }) - const transformed = result.map(({ description, phoneType, country, ...record }) => ({ - ...record, - country: country?.cca2, - phoneType: phoneType ? { key: phoneType?.key.key, defaultText: phoneType?.key.text } : null, - description: description ? { key: description?.tsKey.key, defaultText: description?.tsKey.text } : null, - })) - return transformed - } catch (error) { - handleError(error) } + + const result = await prisma.orgPhone.findMany({ + where: { + ...globalWhere.isPublic(), + ...whereId(), + ...(input.locationOnly !== undefined ? { locationOnly: input.locationOnly } : {}), + }, + select: { + id: true, + number: true, + ext: true, + country: { select: { cca2: true } }, + primary: true, + description: { select: { tsKey: { select: { text: true, key: true } } } }, + phoneType: { select: { key: { select: { text: true, key: true } } } }, + locationOnly: true, + }, + orderBy: { primary: 'desc' }, + }) + const transformed = result.map(({ description, phoneType, country, ...record }) => ({ + ...record, + country: country?.cca2, + phoneType: phoneType ? { key: phoneType?.key.key, defaultText: phoneType?.key.text } : null, + description: description ? { key: description?.tsKey.key, defaultText: description?.tsKey.text } : null, + })) + return transformed } diff --git a/packages/api/router/orgPhone/query.get.handler.ts b/packages/api/router/orgPhone/query.get.handler.ts index 57b5abd4f9..87a7595a63 100644 --- a/packages/api/router/orgPhone/query.get.handler.ts +++ b/packages/api/router/orgPhone/query.get.handler.ts @@ -1,61 +1,56 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetSchema } from './query.get.schema' export const get = async ({ input }: TRPCHandlerParams) => { - try { - const { id, orgLocationId, organizationId, serviceId } = input + const { id, orgLocationId, organizationId, serviceId } = input - const result = await prisma.orgPhone.findMany({ - where: { - id, - ...(organizationId ? { organization: { organizationId } } : {}), - ...(orgLocationId ? { locations: { some: { orgLocationId } } } : {}), - ...(serviceId ? { services: { some: { serviceId } } } : {}), - }, - select: { - id: true, - number: true, - ext: true, - deleted: true, - primary: true, - published: true, - country: { select: { cca2: true, id: true } }, - description: { select: { tsKey: { select: { text: true } } } }, - locations: { select: { location: { select: { id: true, name: true } } } }, - organization: { select: { organization: { select: { id: true, name: true, slug: true } } } }, - phoneType: { select: { id: true, type: true, tsKey: true, tsNs: true } }, - services: { - select: { - service: { - select: { - id: true, - serviceName: { select: { tsKey: { select: { text: true } } } }, - }, + const result = await prisma.orgPhone.findMany({ + where: { + id, + ...(organizationId ? { organization: { organizationId } } : {}), + ...(orgLocationId ? { locations: { some: { orgLocationId } } } : {}), + ...(serviceId ? { services: { some: { serviceId } } } : {}), + }, + select: { + id: true, + number: true, + ext: true, + deleted: true, + primary: true, + published: true, + country: { select: { cca2: true, id: true } }, + description: { select: { tsKey: { select: { text: true } } } }, + locations: { select: { location: { select: { id: true, name: true } } } }, + organization: { select: { organization: { select: { id: true, name: true, slug: true } } } }, + phoneType: { select: { id: true, type: true, tsKey: true, tsNs: true } }, + services: { + select: { + service: { + select: { + id: true, + serviceName: { select: { tsKey: { select: { text: true } } } }, }, }, }, }, - orderBy: [{ published: 'desc' }, { deleted: 'desc' }], - }) + }, + orderBy: [{ published: 'desc' }, { deleted: 'desc' }], + }) - const transformedResult = result.map( - ({ description, locations, organization, phoneType, services, ...record }) => ({ - ...record, - description: description?.tsKey.text, - locations: locations?.map(({ location }) => ({ ...location })), - organization: { ...organization?.organization }, - phoneType, - services: services?.map(({ service }) => ({ - id: service.id, - serviceName: service.serviceName?.tsKey.text, - })), - }) - ) - return transformedResult - } catch (error) { - handleError(error) - } + const transformedResult = result.map( + ({ description, locations, organization, phoneType, services, ...record }) => ({ + ...record, + description: description?.tsKey.text, + locations: locations?.map(({ location }) => ({ ...location })), + organization: { ...organization?.organization }, + phoneType, + services: services?.map(({ service }) => ({ + id: service.id, + serviceName: service.serviceName?.tsKey.text, + })), + }) + ) + return transformedResult } diff --git a/packages/api/router/orgPhone/query.get.schema.ts b/packages/api/router/orgPhone/query.get.schema.ts index 05d5327755..dda81e7dcc 100644 --- a/packages/api/router/orgPhone/query.get.schema.ts +++ b/packages/api/router/orgPhone/query.get.schema.ts @@ -5,26 +5,26 @@ import { prefixedId } from '~api/schemas/idPrefix' export const ZGetSchema = z.union([ z.object({ id: prefixedId('orgPhone'), - organizationId: z.never(), - orgLocationId: z.never(), - serviceId: z.never(), + organizationId: z.undefined().optional(), + orgLocationId: z.undefined().optional(), + serviceId: z.undefined().optional(), }), z.object({ - id: z.never(), + id: z.undefined().optional(), organizationId: prefixedId('organization'), - orgLocationId: z.never(), - serviceId: z.never(), + orgLocationId: z.undefined().optional(), + serviceId: z.undefined().optional(), }), z.object({ - id: z.never(), - organizationId: z.never(), + id: z.undefined().optional(), + organizationId: z.undefined().optional(), orgLocationId: prefixedId('orgLocation'), - serviceId: z.never(), + serviceId: z.undefined().optional(), }), z.object({ - id: z.never(), - organizationId: z.never(), - orgLocationId: z.never(), + id: z.undefined().optional(), + organizationId: z.undefined().optional(), + orgLocationId: z.undefined().optional(), serviceId: prefixedId('orgService'), }), ]) diff --git a/packages/api/router/orgPhoto/mutation.create.handler.ts b/packages/api/router/orgPhoto/mutation.create.handler.ts index 1d355c03ef..963e8f08f3 100644 --- a/packages/api/router/orgPhoto/mutation.create.handler.ts +++ b/packages/api/router/orgPhoto/mutation.create.handler.ts @@ -1,22 +1,17 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateSchema } from './mutation.create.schema' export const create = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) - const newRecord = await prisma.orgPhoto.create({ - data: { - ...input, - auditLogs, - }, - select: { id: true }, - }) - return newRecord - } catch (error) { - handleError(error) - } + const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) + const newRecord = await prisma.orgPhoto.create({ + data: { + ...input, + auditLogs, + }, + select: { id: true }, + }) + return newRecord } diff --git a/packages/api/router/orgPhoto/mutation.update.handler.ts b/packages/api/router/orgPhoto/mutation.update.handler.ts index 59e563f314..2bd4f0f2c6 100644 --- a/packages/api/router/orgPhoto/mutation.update.handler.ts +++ b/packages/api/router/orgPhoto/mutation.update.handler.ts @@ -1,32 +1,27 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TUpdateSchema } from './mutation.update.schema' export const update = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { where, data } = input - const updatedRecord = await prisma.$transaction(async (tx) => { - const current = await tx.orgPhoto.findUniqueOrThrow({ where }) - const auditLogs = CreateAuditLog({ - actorId: ctx.session.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const updated = await tx.orgPhoto.update({ - where, - data: { - ...data, - auditLogs, - }, - }) - return updated + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgPhoto.findUniqueOrThrow({ where }) + const auditLogs = CreateAuditLog({ + actorId: ctx.session.user.id, + operation: 'UPDATE', + from: current, + to: data, }) - return updatedRecord - } catch (error) { - handleError(error) - } + const updated = await tx.orgPhoto.update({ + where, + data: { + ...data, + auditLogs, + }, + }) + return updated + }) + return updatedRecord } diff --git a/packages/api/router/orgPhoto/query.getByParent.handler.ts b/packages/api/router/orgPhoto/query.getByParent.handler.ts index f096ca1741..436bbeca36 100644 --- a/packages/api/router/orgPhoto/query.getByParent.handler.ts +++ b/packages/api/router/orgPhoto/query.getByParent.handler.ts @@ -1,39 +1,34 @@ import { isIdFor, type Prisma, prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetByParentSchema } from './query.getByParent.schema' export const getByParent = async ({ input }: TRPCHandlerParams) => { - try { - const whereId = (): Prisma.OrgPhotoWhereInput => { - switch (true) { - case isIdFor('organization', input): { - return { organization: { id: input, ...globalWhere.isPublic() } } - } - case isIdFor('orgLocation', input): { - return { orgLocation: { id: input, ...globalWhere.isPublic() } } - } - default: { - return {} - } + const whereId = (): Prisma.OrgPhotoWhereInput => { + switch (true) { + case isIdFor('organization', input): { + return { organization: { id: input, ...globalWhere.isPublic() } } + } + case isIdFor('orgLocation', input): { + return { orgLocation: { id: input, ...globalWhere.isPublic() } } + } + default: { + return {} } } - const result = await prisma.orgPhoto.findMany({ - where: { - ...globalWhere.isPublic(), - ...whereId(), - }, - select: { - id: true, - src: true, - height: true, - width: true, - }, - }) - return result - } catch (error) { - handleError(error) } + const result = await prisma.orgPhoto.findMany({ + where: { + ...globalWhere.isPublic(), + ...whereId(), + }, + select: { + id: true, + src: true, + height: true, + width: true, + }, + }) + return result } diff --git a/packages/api/router/orgSocialMedia/mutation.create.handler.ts b/packages/api/router/orgSocialMedia/mutation.create.handler.ts index 0fc60a3ca4..338f966aba 100644 --- a/packages/api/router/orgSocialMedia/mutation.create.handler.ts +++ b/packages/api/router/orgSocialMedia/mutation.create.handler.ts @@ -1,22 +1,17 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateSchema } from './mutation.create.schema' export const create = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) - const newSocial = await prisma.orgSocialMedia.create({ - data: { - ...input, - auditLogs, - }, - select: { id: true }, - }) - return newSocial - } catch (error) { - handleError(error) - } + const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) + const newSocial = await prisma.orgSocialMedia.create({ + data: { + ...input, + auditLogs, + }, + select: { id: true }, + }) + return newSocial } diff --git a/packages/api/router/orgSocialMedia/mutation.update.handler.ts b/packages/api/router/orgSocialMedia/mutation.update.handler.ts index bece7aaa86..1b2c52017a 100644 --- a/packages/api/router/orgSocialMedia/mutation.update.handler.ts +++ b/packages/api/router/orgSocialMedia/mutation.update.handler.ts @@ -1,32 +1,27 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TUpdateSchema } from './mutation.update.schema' export const update = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { where, data } = input - const updatedRecord = await prisma.$transaction(async (tx) => { - const current = await tx.orgSocialMedia.findUniqueOrThrow({ where }) - const auditLogs = CreateAuditLog({ - actorId: ctx.session.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const updated = await tx.orgSocialMedia.update({ - where, - data: { - ...data, - auditLogs, - }, - }) - return updated + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgSocialMedia.findUniqueOrThrow({ where }) + const auditLogs = CreateAuditLog({ + actorId: ctx.session.user.id, + operation: 'UPDATE', + from: current, + to: data, }) - return updatedRecord - } catch (error) { - handleError(error) - } + const updated = await tx.orgSocialMedia.update({ + where, + data: { + ...data, + auditLogs, + }, + }) + return updated + }) + return updatedRecord } diff --git a/packages/api/router/orgSocialMedia/query.forContactInfo.handler.ts b/packages/api/router/orgSocialMedia/query.forContactInfo.handler.ts index 1f36dd941d..076ab07aea 100644 --- a/packages/api/router/orgSocialMedia/query.forContactInfo.handler.ts +++ b/packages/api/router/orgSocialMedia/query.forContactInfo.handler.ts @@ -1,46 +1,41 @@ import { isIdFor, type Prisma, prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' import { type TForContactInfoSchema } from './query.forContactInfo.schema' export const forContactInfo = async ({ input }: TRPCHandlerParams) => { - try { - const whereId = (): Prisma.OrgSocialMediaWhereInput => { - switch (true) { - case isIdFor('organization', input.parentId): { - return { organization: { id: input.parentId, ...globalWhere.isPublic() } } - } - case isIdFor('orgLocation', input.parentId): { - return { orgLocation: { id: input.parentId, ...globalWhere.isPublic() } } - } - default: { - return {} - } + const whereId = (): Prisma.OrgSocialMediaWhereInput => { + switch (true) { + case isIdFor('organization', input.parentId): { + return { organization: { id: input.parentId, ...globalWhere.isPublic() } } + } + case isIdFor('orgLocation', input.parentId): { + return { orgLocation: { id: input.parentId, ...globalWhere.isPublic() } } + } + default: { + return {} } } - - const result = await prisma.orgSocialMedia.findMany({ - where: { - ...globalWhere.isPublic(), - ...whereId(), - ...(input.locationOnly !== undefined ? { locationOnly: input.locationOnly } : {}), - }, - select: { - id: true, - url: true, - username: true, - service: { select: { name: true } }, - orgLocationOnly: true, - }, - }) - const transformed = result.map(({ service, ...record }) => ({ - ...record, - service: service?.name, - })) - return transformed - } catch (error) { - handleError(error) } + + const result = await prisma.orgSocialMedia.findMany({ + where: { + ...globalWhere.isPublic(), + ...whereId(), + ...(input.locationOnly !== undefined ? { locationOnly: input.locationOnly } : {}), + }, + select: { + id: true, + url: true, + username: true, + service: { select: { name: true } }, + orgLocationOnly: true, + }, + }) + const transformed = result.map(({ service, ...record }) => ({ + ...record, + service: service?.name, + })) + return transformed } diff --git a/packages/api/router/orgWebsite/mutation.create.handler.ts b/packages/api/router/orgWebsite/mutation.create.handler.ts index 2f562cd5e2..364c7dbc00 100644 --- a/packages/api/router/orgWebsite/mutation.create.handler.ts +++ b/packages/api/router/orgWebsite/mutation.create.handler.ts @@ -1,22 +1,17 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateSchema } from './mutation.create.schema' export const create = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) - const newRecord = await prisma.orgWebsite.create({ - data: { - ...input, - auditLogs, - }, - select: { id: true }, - }) - return newRecord - } catch (error) { - handleError(error) - } + const auditLogs = CreateAuditLog({ actorId: ctx.session.user.id, operation: 'CREATE', to: input }) + const newRecord = await prisma.orgWebsite.create({ + data: { + ...input, + auditLogs, + }, + select: { id: true }, + }) + return newRecord } diff --git a/packages/api/router/orgWebsite/mutation.update.handler.ts b/packages/api/router/orgWebsite/mutation.update.handler.ts index d9de09a561..bfa43e3f12 100644 --- a/packages/api/router/orgWebsite/mutation.update.handler.ts +++ b/packages/api/router/orgWebsite/mutation.update.handler.ts @@ -1,32 +1,27 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TUpdateSchema } from './mutation.update.schema' export const update = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { where, data } = input - const updatedRecord = await prisma.$transaction(async (tx) => { - const current = await tx.orgWebsite.findUniqueOrThrow({ where }) - const auditLogs = CreateAuditLog({ - actorId: ctx.session.user.id, - operation: 'UPDATE', - from: current, - to: data, - }) - const updated = await tx.orgWebsite.update({ - where, - data: { - ...data, - auditLogs, - }, - }) - return updated + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgWebsite.findUniqueOrThrow({ where }) + const auditLogs = CreateAuditLog({ + actorId: ctx.session.user.id, + operation: 'UPDATE', + from: current, + to: data, }) - return updatedRecord - } catch (error) { - handleError(error) - } + const updated = await tx.orgWebsite.update({ + where, + data: { + ...data, + auditLogs, + }, + }) + return updated + }) + return updatedRecord } diff --git a/packages/api/router/orgWebsite/query.forContactInfo.handler.ts b/packages/api/router/orgWebsite/query.forContactInfo.handler.ts index 2b7c47ca78..d9ea14ff11 100644 --- a/packages/api/router/orgWebsite/query.forContactInfo.handler.ts +++ b/packages/api/router/orgWebsite/query.forContactInfo.handler.ts @@ -1,50 +1,43 @@ import { isIdFor, prisma, type Prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' import { type TForContactInfoSchema } from './query.forContactInfo.schema' export const forContactInfo = async ({ input }: TRPCHandlerParams) => { - try { - const whereId = (): Prisma.OrgWebsiteWhereInput => { - switch (true) { - case isIdFor('organization', input.parentId): { - return { organization: { id: input.parentId, ...globalWhere.isPublic() } } - } - case isIdFor('orgLocation', input.parentId): { - return { orgLocation: { id: input.parentId, ...globalWhere.isPublic() } } - } - // case isIdFor('orgService', input.parentId): { - // return { services: { some: { service: { id: input.parentId, ...globalWhere.isPublic() } } } } - // } - default: { - return {} - } + const whereId = (): Prisma.OrgWebsiteWhereInput => { + switch (true) { + case isIdFor('organization', input.parentId): { + return { organization: { id: input.parentId, ...globalWhere.isPublic() } } + } + case isIdFor('orgLocation', input.parentId): { + return { orgLocation: { id: input.parentId, ...globalWhere.isPublic() } } } - } - const result = await prisma.orgWebsite.findMany({ - where: { - ...globalWhere.isPublic(), - ...whereId(), - ...(input.locationOnly !== undefined ? { orgLocationOnly: input.locationOnly } : {}), - }, - select: { - id: true, - url: true, - isPrimary: true, - description: { select: { tsKey: { select: { text: true, key: true } } } }, - orgLocationOnly: true, - }, - orderBy: { isPrimary: 'desc' }, - }) - const transformed = result.map(({ description, ...record }) => ({ - ...record, - description: description ? { key: description?.tsKey.key, defaultText: description?.tsKey.text } : null, - })) - return transformed - } catch (error) { - handleError(error) + default: { + return {} + } + } } + + const result = await prisma.orgWebsite.findMany({ + where: { + ...globalWhere.isPublic(), + ...whereId(), + ...(input.locationOnly !== undefined ? { orgLocationOnly: input.locationOnly } : {}), + }, + select: { + id: true, + url: true, + isPrimary: true, + description: { select: { tsKey: { select: { text: true, key: true } } } }, + orgLocationOnly: true, + }, + orderBy: { isPrimary: 'desc' }, + }) + const transformed = result.map(({ description, ...record }) => ({ + ...record, + description: description ? { key: description?.tsKey.key, defaultText: description?.tsKey.text } : null, + })) + return transformed } diff --git a/packages/api/router/organization/mutation.attachAttribute.handler.ts b/packages/api/router/organization/mutation.attachAttribute.handler.ts index 2d49695874..47c7f92979 100644 --- a/packages/api/router/organization/mutation.attachAttribute.handler.ts +++ b/packages/api/router/organization/mutation.attachAttribute.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TAttachAttributeSchema, ZAttachAttributeSchema } from './mutation.attachAttribute.schema' @@ -8,27 +7,23 @@ export const attachAttribute = async ({ ctx, input, }: TRPCHandlerParams) => { - try { - const inputData = { actorId: ctx.session.user.id, operation: 'LINK', data: input } - const { translationKey, freeText, attributeSupplement, organizationAttribute, auditLogs } = - ZAttachAttributeSchema().dataParser.parse(inputData) + const inputData = { actorId: ctx.session.user.id, operation: 'LINK', data: input } + const { translationKey, freeText, attributeSupplement, organizationAttribute, auditLogs } = + ZAttachAttributeSchema().dataParser.parse(inputData) - const result = await prisma.$transaction(async (tx) => { - const tKey = translationKey ? await tx.translationKey.create(translationKey) : undefined - const fText = freeText ? await tx.freeText.create(freeText) : undefined - const aSupp = attributeSupplement ? await tx.attributeSupplement.create(attributeSupplement) : undefined - const attrLink = await tx.organizationAttribute.create(organizationAttribute) - const logs = await tx.auditLog.createMany({ data: auditLogs, skipDuplicates: true }) - return { - translationKey: tKey, - freeText: fText, - attributeSupplement: aSupp, - organizationAttribute: attrLink, - auditLog: logs, - } - }) - return result - } catch (error) { - handleError(error) - } + const result = await prisma.$transaction(async (tx) => { + const tKey = translationKey ? await tx.translationKey.create(translationKey) : undefined + const fText = freeText ? await tx.freeText.create(freeText) : undefined + const aSupp = attributeSupplement ? await tx.attributeSupplement.create(attributeSupplement) : undefined + const attrLink = await tx.organizationAttribute.create(organizationAttribute) + const logs = await tx.auditLog.createMany({ data: auditLogs, skipDuplicates: true }) + return { + translationKey: tKey, + freeText: fText, + attributeSupplement: aSupp, + organizationAttribute: attrLink, + auditLog: logs, + } + }) + return result } diff --git a/packages/api/router/organization/mutation.createNewQuick.handler.ts b/packages/api/router/organization/mutation.createNewQuick.handler.ts index 82b2c40bf1..4480d90e0c 100644 --- a/packages/api/router/organization/mutation.createNewQuick.handler.ts +++ b/packages/api/router/organization/mutation.createNewQuick.handler.ts @@ -1,7 +1,6 @@ import { type z } from 'zod' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateNewQuickSchema, ZCreateNewQuickSchema } from './mutation.createNewQuick.schema' @@ -10,19 +9,15 @@ export const createNewQuick = async ({ ctx, input, }: TRPCHandlerParams) => { - try { - const inputData = { - actorId: ctx.session.user.id, - operation: 'CREATE', - data: input, - } satisfies z.input['dataParser']> + const inputData = { + actorId: ctx.session.user.id, + operation: 'CREATE', + data: input, + } satisfies z.input['dataParser']> - const record = ZCreateNewQuickSchema().dataParser.parse(inputData) + const record = ZCreateNewQuickSchema().dataParser.parse(inputData) - const result = await prisma.organization.create(record) + const result = await prisma.organization.create(record) - return result - } catch (error) { - handleError(error) - } + return result } diff --git a/packages/api/router/organization/mutation.createNewSuggestion.handler.ts b/packages/api/router/organization/mutation.createNewSuggestion.handler.ts index 9715c1b941..03a6535a87 100644 --- a/packages/api/router/organization/mutation.createNewSuggestion.handler.ts +++ b/packages/api/router/organization/mutation.createNewSuggestion.handler.ts @@ -1,7 +1,6 @@ import { type z } from 'zod' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { @@ -13,17 +12,13 @@ export const createNewSuggestion = async ({ ctx, input, }: TRPCHandlerParams) => { - try { - const inputData = { - actorId: ctx.session.user.id, - operation: 'CREATE', - data: input, - } satisfies z.input['dataParser']> + const inputData = { + actorId: ctx.session.user.id, + operation: 'CREATE', + data: input, + } satisfies z.input['dataParser']> - const record = ZCreateNewSuggestionSchema().dataParser.parse(inputData) - const result = await prisma.suggestion.create(record) - return result - } catch (error) { - handleError(error) - } + const record = ZCreateNewSuggestionSchema().dataParser.parse(inputData) + const result = await prisma.suggestion.create(record) + return result } diff --git a/packages/api/router/organization/query.checkForExisting.handler.ts b/packages/api/router/organization/query.checkForExisting.handler.ts index a4797283be..fee9ccd497 100644 --- a/packages/api/router/organization/query.checkForExisting.handler.ts +++ b/packages/api/router/organization/query.checkForExisting.handler.ts @@ -1,26 +1,21 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCheckForExistingSchema } from './query.checkForExisting.schema' export const checkForExisting = async ({ input }: TRPCHandlerParams) => { - try { - const result = await prisma.organization.findFirst({ - where: { - name: { - contains: input, - mode: 'insensitive', - }, + const result = await prisma.organization.findFirst({ + where: { + name: { + contains: input, + mode: 'insensitive', }, - select: { - name: true, - slug: true, - published: true, - }, - }) - return result - } catch (error) { - handleError(error) - } + }, + select: { + name: true, + slug: true, + published: true, + }, + }) + return result } diff --git a/packages/api/router/organization/query.forLocationPage.handler.ts b/packages/api/router/organization/query.forLocationPage.handler.ts index 36810f83d7..c1e7ea12de 100644 --- a/packages/api/router/organization/query.forLocationPage.handler.ts +++ b/packages/api/router/organization/query.forLocationPage.handler.ts @@ -1,35 +1,30 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { isPublic } from '~api/schemas/selects/common' import { type TRPCHandlerParams } from '~api/types/handler' import { type TForLocationPageSchema } from './query.forLocationPage.schema' export const forLocationPage = async ({ input }: TRPCHandlerParams) => { - try { - const { slug } = input - const org = await prisma.organization.findUniqueOrThrow({ - where: { - slug, - ...isPublic, - }, - select: { - id: true, - name: true, - slug: true, - published: true, - lastVerified: true, - allowedEditors: { where: { authorized: true }, select: { userId: true } }, - }, - }) - const { allowedEditors, ...orgData } = org - const reformatted = { - ...orgData, - isClaimed: Boolean(allowedEditors.length), - } - - return reformatted - } catch (error) { - handleError(error) + const { slug } = input + const org = await prisma.organization.findUniqueOrThrow({ + where: { + slug, + ...isPublic, + }, + select: { + id: true, + name: true, + slug: true, + published: true, + lastVerified: true, + allowedEditors: { where: { authorized: true }, select: { userId: true } }, + }, + }) + const { allowedEditors, ...orgData } = org + const reformatted = { + ...orgData, + isClaimed: Boolean(allowedEditors.length), } + + return reformatted } diff --git a/packages/api/router/organization/query.forOrgPage.handler.ts b/packages/api/router/organization/query.forOrgPage.handler.ts index f69a4c9f42..e7dd1574c3 100644 --- a/packages/api/router/organization/query.forOrgPage.handler.ts +++ b/packages/api/router/organization/query.forOrgPage.handler.ts @@ -1,59 +1,54 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { attributes, freeText, isPublic } from '~api/schemas/selects/common' import { type TRPCHandlerParams } from '~api/types/handler' import { type TForOrgPageSchema } from './query.forOrgPage.schema' export const forOrgPage = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { slug } = input - const org = await prisma.organization.findUniqueOrThrow({ - where: { - slug, - ...isPublic, + const { slug } = input + const org = await prisma.organization.findUniqueOrThrow({ + where: { + slug, + ...isPublic, + }, + select: { + id: true, + name: true, + slug: true, + published: true, + lastVerified: true, + allowedEditors: { where: { authorized: true }, select: { userId: true } }, + description: freeText, + userLists: ctx.session?.user.id + ? { + where: { list: { ownedById: ctx.session.user.id } }, + select: { list: { select: { id: true, name: true } } }, + } + : undefined, + attributes, + reviews: { + where: { visible: true, deleted: false }, + select: { id: true }, }, - select: { - id: true, - name: true, - slug: true, - published: true, - lastVerified: true, - allowedEditors: { where: { authorized: true }, select: { userId: true } }, - description: freeText, - userLists: ctx.session?.user.id - ? { - where: { list: { ownedById: ctx.session.user.id } }, - select: { list: { select: { id: true, name: true } } }, - } - : undefined, - attributes, - reviews: { - where: { visible: true, deleted: false }, - select: { id: true }, - }, - locations: { - where: isPublic, - select: { - id: true, - street1: true, - street2: true, - city: true, - postCode: true, - country: { select: { cca2: true } }, - govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, - }, + locations: { + where: isPublic, + select: { + id: true, + street1: true, + street2: true, + city: true, + postCode: true, + country: { select: { cca2: true } }, + govDist: { select: { abbrev: true, tsKey: true, tsNs: true } }, }, }, - }) - const { allowedEditors, ...orgData } = org - const reformatted = { - ...orgData, - isClaimed: Boolean(allowedEditors.length), - } - - return reformatted - } catch (error) { - handleError(error) + }, + }) + const { allowedEditors, ...orgData } = org + const reformatted = { + ...orgData, + isClaimed: Boolean(allowedEditors.length), } + + return reformatted } diff --git a/packages/api/router/organization/query.generateSlug.handler.ts b/packages/api/router/organization/query.generateSlug.handler.ts index 9fe4f76cc5..f473115bbb 100644 --- a/packages/api/router/organization/query.generateSlug.handler.ts +++ b/packages/api/router/organization/query.generateSlug.handler.ts @@ -1,7 +1,6 @@ import slugify from 'slugify' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGenerateSlugSchema } from './query.generateSlug.schema' @@ -30,10 +29,6 @@ const uniqueSlug = async (name: string, inc?: number): Promise => { } export const generateSlug = async ({ input }: TRPCHandlerParams) => { - try { - const slug = await uniqueSlug(input) - return slug - } catch (error) { - handleError(error) - } + const slug = await uniqueSlug(input) + return slug } diff --git a/packages/api/router/organization/query.getById.handler.ts b/packages/api/router/organization/query.getById.handler.ts index 94e1d27c96..4ac5d6f57c 100644 --- a/packages/api/router/organization/query.getById.handler.ts +++ b/packages/api/router/organization/query.getById.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { isPublic } from '~api/schemas/selects/common' import { organizationInclude } from '~api/schemas/selects/org' import { type TRPCHandlerParams } from '~api/types/handler' @@ -7,24 +6,20 @@ import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetByIdSchema } from './query.getById.schema' export const getById = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { select } = organizationInclude(ctx) - const org = await prisma.organization.findUniqueOrThrow({ - where: { - id: input.id, - ...isPublic, - }, - select, - }) - const { allowedEditors, ...orgData } = org - const reformatted = { - ...orgData, - isClaimed: Boolean(allowedEditors.length), - services: org.services.map((serv) => ({ service: serv })), - } - - return reformatted - } catch (error) { - handleError(error) + const { select } = organizationInclude(ctx) + const org = await prisma.organization.findUniqueOrThrow({ + where: { + id: input.id, + ...isPublic, + }, + select, + }) + const { allowedEditors, ...orgData } = org + const reformatted = { + ...orgData, + isClaimed: Boolean(allowedEditors.length), + services: org.services.map((serv) => ({ service: serv })), } + + return reformatted } diff --git a/packages/api/router/organization/query.getBySlug.handler.ts b/packages/api/router/organization/query.getBySlug.handler.ts index 58d2130344..3119546db2 100644 --- a/packages/api/router/organization/query.getBySlug.handler.ts +++ b/packages/api/router/organization/query.getBySlug.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { isPublic } from '~api/schemas/selects/common' import { organizationInclude } from '~api/schemas/selects/org' import { type TRPCHandlerParams } from '~api/types/handler' @@ -7,25 +6,21 @@ import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetBySlugSchema } from './query.getBySlug.schema' export const getBySlug = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { slug } = input - const { select } = organizationInclude(ctx) - const org = await prisma.organization.findUniqueOrThrow({ - where: { - slug, - ...isPublic, - }, - select, - }) - const { allowedEditors, ...orgData } = org - const reformatted = { - ...orgData, - isClaimed: Boolean(allowedEditors.length), - services: org.services.map((serv) => ({ service: serv })), - } - - return reformatted - } catch (error) { - handleError(error) + const { slug } = input + const { select } = organizationInclude(ctx) + const org = await prisma.organization.findUniqueOrThrow({ + where: { + slug, + ...isPublic, + }, + select, + }) + const { allowedEditors, ...orgData } = org + const reformatted = { + ...orgData, + isClaimed: Boolean(allowedEditors.length), + services: org.services.map((serv) => ({ service: serv })), } + + return reformatted } diff --git a/packages/api/router/organization/query.getIdFromSlug.handler.ts b/packages/api/router/organization/query.getIdFromSlug.handler.ts index 7537174843..9d335f1a47 100644 --- a/packages/api/router/organization/query.getIdFromSlug.handler.ts +++ b/packages/api/router/organization/query.getIdFromSlug.handler.ts @@ -1,23 +1,18 @@ import { prisma } from '@weareinreach/db' import { readSlugCache, writeSlugCache } from '~api/cache/slugToOrgId' -import { handleError } from '~api/lib/errorHandler' import { isPublic } from '~api/schemas/selects/common' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetIdFromSlugSchema } from './query.getIdFromSlug.schema' export const getIdFromSlug = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { slug } = input - const cachedId = await readSlugCache(slug) - if (cachedId) return { id: cachedId } - const orgId = await prisma.organization.findUniqueOrThrow({ - where: { slug, ...isPublic }, - select: { id: true }, - }) - await writeSlugCache(slug, orgId.id) - return orgId - } catch (error) { - handleError(error) - } + const { slug } = input + const cachedId = await readSlugCache(slug) + if (cachedId) return { id: cachedId } + const orgId = await prisma.organization.findUniqueOrThrow({ + where: { slug, ...isPublic }, + select: { id: true }, + }) + await writeSlugCache(slug, orgId.id) + return orgId } diff --git a/packages/api/router/organization/query.getIntlCrisis.handler.ts b/packages/api/router/organization/query.getIntlCrisis.handler.ts index e6fa23743b..034d28c96d 100644 --- a/packages/api/router/organization/query.getIntlCrisis.handler.ts +++ b/packages/api/router/organization/query.getIntlCrisis.handler.ts @@ -2,8 +2,7 @@ import superjson from 'superjson' import { type SuperJSONResult } from 'superjson/dist/types' import { z } from 'zod' -import { prisma, type Prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' +import { prisma } from '@weareinreach/db' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetIntlCrisisSchema } from './query.getIntlCrisis.schema' @@ -14,94 +13,90 @@ const isSuperJSON = (input: unknown): input is SuperJSONResult => const AccessInstructionSchema = z.object({ access_type: z.string(), access_value: z.string() }) export const getIntlCrisis = async ({ input }: TRPCHandlerParams) => { - try { - const orgs = await prisma.organization.findMany({ - where: { - serviceAreas: { active: true, countries: { some: { country: { cca2: input.cca2 } } } }, - crisisResource: true, - published: true, - deleted: false, - }, - select: { - id: true, - name: true, - description: { select: { tsKey: { select: { key: true, text: true } } } }, - attributes: { - where: { - active: true, - attribute: { tag: 'other-describe', active: true }, - }, - select: { - attribute: { select: { tag: true } }, - supplement: { select: { text: { select: { tsKey: { select: { key: true, text: true } } } } } }, - }, + const orgs = await prisma.organization.findMany({ + where: { + serviceAreas: { active: true, countries: { some: { country: { cca2: input.cca2 } } } }, + crisisResource: true, + published: true, + deleted: false, + }, + select: { + id: true, + name: true, + description: { select: { tsKey: { select: { key: true, text: true } } } }, + attributes: { + where: { + active: true, + attribute: { tag: 'other-describe', active: true }, }, - services: { - select: { - accessDetails: { - where: { - active: true, - attribute: { active: true }, - }, - select: { - attribute: { select: { tag: true } }, - supplement: { - select: { data: true }, - }, - }, + select: { + attribute: { select: { tag: true } }, + supplement: { select: { text: { select: { tsKey: { select: { key: true, text: true } } } } } }, + }, + }, + services: { + select: { + accessDetails: { + where: { + active: true, + attribute: { active: true }, }, - services: { - where: { active: true, tag: { active: true } }, - select: { - tag: { select: { tsKey: true, tsNs: true } }, + select: { + attribute: { select: { tag: true } }, + supplement: { + select: { data: true }, }, }, }, + services: { + where: { active: true, tag: { active: true } }, + select: { + tag: { select: { tsKey: true, tsNs: true } }, + }, + }, }, }, - orderBy: { - crisisResourceSort: 'asc', - }, - }) + }, + orderBy: { + crisisResourceSort: 'asc', + }, + }) - const formattedData = orgs.map(({ id, name, description, attributes, services }) => { - const serviceTags: Record<'tsKey' | 'tsNs', string>[] = [] - const accessInstructions: Record<'tag' | 'access_type' | 'access_value', string>[] = [] + const formattedData = orgs.map(({ id, name, description, attributes, services }) => { + const serviceTags: Record<'tsKey' | 'tsNs', string>[] = [] + const accessInstructions: Record<'tag' | 'access_type' | 'access_value', string>[] = [] - for (const { accessDetails, services: service } of services) { - for (const { tag } of service) { - serviceTags.push(tag) - } - for (const { attribute, supplement } of accessDetails) { - for (const { data } of supplement) { - const parsedData = AccessInstructionSchema.parse( - isSuperJSON(data) - ? superjson.deserialize<{ access_type: string; access_value: string }>(data) - : data - ) - accessInstructions.push({ tag: attribute.tag, ...parsedData }) - } + for (const { accessDetails, services: service } of services) { + for (const { tag } of service) { + serviceTags.push(tag) + } + for (const { attribute, supplement } of accessDetails) { + for (const { data } of supplement) { + const parsedData = AccessInstructionSchema.parse( + isSuperJSON(data) + ? superjson.deserialize<{ access_type: string; access_value: string }>(data) + : data + ) + accessInstructions.push({ tag: attribute.tag, ...parsedData }) } } + } - return { - id, - name, - description: { ...description?.tsKey }, - targetPop: attributes - .map(({ attribute, supplement }) => ({ - tag: attribute.tag, - text: supplement.at(0)?.text?.tsKey.text, - tsKey: supplement.at(0)?.text?.tsKey.key, - })) - .at(0), - services: serviceTags, - accessInstructions, - } - }) + return { + id, + name, + description: { ...description?.tsKey }, + targetPop: attributes + .map(({ attribute, supplement }) => ({ + tag: attribute.tag, + text: supplement.at(0)?.text?.tsKey.text, + tsKey: supplement.at(0)?.text?.tsKey.key, + })) + .at(0), + services: serviceTags, + accessInstructions, + } + }) - return formattedData - } catch (error) { - handleError(error) - } + return formattedData } diff --git a/packages/api/router/organization/query.getNameFromSlug.handler.ts b/packages/api/router/organization/query.getNameFromSlug.handler.ts index 3cda6e039a..eedf5a0c18 100644 --- a/packages/api/router/organization/query.getNameFromSlug.handler.ts +++ b/packages/api/router/organization/query.getNameFromSlug.handler.ts @@ -1,21 +1,16 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetNameFromSlugSchema } from './query.getNameFromSlug.schema' export const getNameFromSlug = async ({ input }: TRPCHandlerParams) => { - try { - const result = prisma.organization.findUniqueOrThrow({ - where: { - slug: input, - }, - select: { - name: true, - }, - }) - return result - } catch (error) { - handleError(error) - } + const result = prisma.organization.findUniqueOrThrow({ + where: { + slug: input, + }, + select: { + name: true, + }, + }) + return result } diff --git a/packages/api/router/organization/query.getNatlCrisis.handler.ts b/packages/api/router/organization/query.getNatlCrisis.handler.ts index d5bf521a1a..0fb2811a6a 100644 --- a/packages/api/router/organization/query.getNatlCrisis.handler.ts +++ b/packages/api/router/organization/query.getNatlCrisis.handler.ts @@ -1,10 +1,8 @@ import superjson from 'superjson' import { type SuperJSONResult } from 'superjson/dist/types' -import { z } from 'zod' -import { prisma, type Prisma } from '@weareinreach/db' +import { prisma } from '@weareinreach/db' import { accessInstructions } from '@weareinreach/db/zod_util/attributeSupplement' -import { handleError } from '~api/lib/errorHandler' import { isPublic } from '~api/schemas/selects/common' import { type TRPCHandlerParams } from '~api/types/handler' @@ -16,103 +14,97 @@ const isSuperJSON = (input: unknown): input is SuperJSONResult => const AccessInstructionSchema = accessInstructions.getAll() export const getNatlCrisis = async ({ input }: TRPCHandlerParams) => { - try { - const orgs = await prisma.organization.findMany({ - where: { - services: { - some: { - crisisSupportOnly: true, - serviceAreas: { active: true, countries: { some: { country: { cca2: input.cca2 } } } }, - }, + const orgs = await prisma.organization.findMany({ + where: { + services: { + some: { + crisisSupportOnly: true, + serviceAreas: { active: true, countries: { some: { country: { cca2: input.cca2 } } } }, }, - // serviceAreas: { active: true, countries: { some: { country: { cca2: input.cca2 } } } }, - // crisisResource: true, - published: true, - deleted: false, }, - select: { - id: true, - name: true, - services: { - where: { - crisisSupportOnly: true, - serviceAreas: { active: true, countries: { some: { country: { cca2: input.cca2 } } } }, - ...isPublic, - }, - select: { - description: { - select: { - tsKey: { - select: { - text: true, - key: true, - }, + published: true, + deleted: false, + }, + select: { + id: true, + name: true, + services: { + where: { + crisisSupportOnly: true, + serviceAreas: { active: true, countries: { some: { country: { cca2: input.cca2 } } } }, + ...isPublic, + }, + select: { + description: { + select: { + tsKey: { + select: { + text: true, + key: true, }, }, }, - accessDetails: { - where: { - active: true, - attribute: { active: true }, - }, - select: { - attribute: { select: { tag: true } }, - supplement: { - select: { data: true, text: { select: { tsKey: { select: { key: true, text: true } } } } }, - }, + }, + accessDetails: { + where: { + active: true, + attribute: { active: true }, + }, + select: { + attribute: { select: { tag: true } }, + supplement: { + select: { data: true, text: { select: { tsKey: { select: { key: true, text: true } } } } }, }, }, - attributes: { - where: { active: true }, - select: { - attribute: { - select: { - icon: true, - tsKey: true, - }, + }, + attributes: { + where: { active: true }, + select: { + attribute: { + select: { + icon: true, + tsKey: true, }, }, }, }, }, }, - orderBy: { - crisisResourceSort: 'asc', - }, - }) + }, + orderBy: { + crisisResourceSort: 'asc', + }, + }) - const formattedData = orgs.map(({ id, name, services }) => { - const attributeTags: (Record<'tsKey', string> & { icon: string | null })[] = [] - const accessInstructions: Record[] = [] - let descriptionText: Record<'key' | 'text', string> | null = null - for (const { accessDetails, attributes, description } of services) { - if (description) descriptionText = description.tsKey - for (const { attribute } of attributes) { - attributeTags.push(attribute) - } - for (const { attribute, supplement } of accessDetails) { - for (const { data, text } of supplement) { - const parsedData = AccessInstructionSchema.parse( - isSuperJSON(data) - ? superjson.deserialize<{ access_type: string; access_value: string }>(data) - : data - ) - accessInstructions.push({ tag: attribute.tag, ...parsedData, ...text?.tsKey }) - } + const formattedData = orgs.map(({ id, name, services }) => { + const attributeTags: (Record<'tsKey', string> & { icon: string | null })[] = [] + const accessInstructions: Record[] = [] + let descriptionText: Record<'key' | 'text', string> | null = null + for (const { accessDetails, attributes, description } of services) { + if (description) descriptionText = description.tsKey + for (const { attribute } of attributes) { + attributeTags.push(attribute) + } + for (const { attribute, supplement } of accessDetails) { + for (const { data, text } of supplement) { + const parsedData = AccessInstructionSchema.parse( + isSuperJSON(data) + ? superjson.deserialize<{ access_type: string; access_value: string }>(data) + : data + ) + accessInstructions.push({ tag: attribute.tag, ...parsedData, ...text?.tsKey }) } } + } - return { - id, - name, - description: descriptionText, - community: attributeTags.at(0), - accessInstructions, - } - }) + return { + id, + name, + description: descriptionText, + community: attributeTags.at(0), + accessInstructions, + } + }) - return formattedData - } catch (error) { - handleError(error) - } + return formattedData } diff --git a/packages/api/router/organization/query.isSaved.handler.ts b/packages/api/router/organization/query.isSaved.handler.ts index f04d4c5dad..05beedd999 100644 --- a/packages/api/router/organization/query.isSaved.handler.ts +++ b/packages/api/router/organization/query.isSaved.handler.ts @@ -1,36 +1,31 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TIsSavedSchema } from './query.isSaved.schema' export const isSaved = async ({ ctx, input }: TRPCHandlerParams) => { - try { - if (!ctx.session?.user.id) return false + if (!ctx.session?.user.id) return false - const listEntries = await prisma.savedOrganization.findMany({ - where: { - list: { - ownedById: ctx.session.user.id, - }, - organization: { - slug: input, - }, + const listEntries = await prisma.savedOrganization.findMany({ + where: { + list: { + ownedById: ctx.session.user.id, + }, + organization: { + slug: input, }, - select: { - list: { - select: { - id: true, - name: true, - }, + }, + select: { + list: { + select: { + id: true, + name: true, }, }, - }) + }, + }) - if (!listEntries.length) return false - const lists = listEntries.map(({ list }) => list) - return lists - } catch (error) { - handleError(error) - } + if (!listEntries.length) return false + const lists = listEntries.map(({ list }) => list) + return lists } diff --git a/packages/api/router/organization/query.searchDistance.handler.ts b/packages/api/router/organization/query.searchDistance.handler.ts index 89c6d78c2f..7e8ebef02b 100644 --- a/packages/api/router/organization/query.searchDistance.handler.ts +++ b/packages/api/router/organization/query.searchDistance.handler.ts @@ -1,7 +1,6 @@ import { getDistance } from 'geolib' import { prisma, Prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { isPublic } from '~api/schemas/selects/common' import { orgSearchSelect } from '~api/schemas/selects/org' import { type TRPCHandlerParams } from '~api/types/handler' @@ -290,26 +289,22 @@ type City = { } export const searchDistance = async ({ input }: TRPCHandlerParams) => { - try { - const { unit } = input + const { unit } = input - const orgs = await searchOrgByDistance(input) - const resultIds = orgs.results.map(({ id }) => id) + const orgs = await searchOrgByDistance(input) + const resultIds = orgs.results.map(({ id }) => id) - const results = await prismaDistSearchDetails({ ...input, resultIds }) + const results = await prismaDistSearchDetails({ ...input, resultIds }) - const orderedResults: ((typeof results)[number] & { - distance: number - unit: 'km' | 'mi' - national: string[] - })[] = [] - orgs.results.forEach(({ id, distMeters, national }) => { - const distance = unit === 'km' ? distMeters / 1000 : distMeters / 1000 / 1.60934 - const sort = results.find((result) => result.id === id) - if (sort) orderedResults.push({ ...sort, distance: +distance.toFixed(2), unit, national }) - }) - return { orgs: orderedResults, resultCount: orgs.total } - } catch (error) { - handleError(error) - } + const orderedResults: ((typeof results)[number] & { + distance: number + unit: 'km' | 'mi' + national: string[] + })[] = [] + orgs.results.forEach(({ id, distMeters, national }) => { + const distance = unit === 'km' ? distMeters / 1000 : distMeters / 1000 / 1.60934 + const sort = results.find((result) => result.id === id) + if (sort) orderedResults.push({ ...sort, distance: +distance.toFixed(2), unit, national }) + }) + return { orgs: orderedResults, resultCount: orgs.total } } diff --git a/packages/api/router/organization/query.searchName.handler.ts b/packages/api/router/organization/query.searchName.handler.ts index d3101c2880..a5a588a120 100644 --- a/packages/api/router/organization/query.searchName.handler.ts +++ b/packages/api/router/organization/query.searchName.handler.ts @@ -1,30 +1,24 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { isPublic } from '~api/schemas/selects/common' import { type TRPCHandlerParams } from '~api/types/handler' import { type TSearchNameSchema } from './query.searchName.schema' export const searchName = async ({ input }: TRPCHandlerParams) => { - try { - const orgIds = await prisma.organization.findMany({ - where: { - name: { - contains: input.search, - mode: 'insensitive', - }, - ...isPublic, + const orgIds = await prisma.organization.findMany({ + where: { + name: { + contains: input.search, + mode: 'insensitive', }, - select: { - id: true, - name: true, - slug: true, - }, - }) - const shaped = orgIds.map(({ name, ...rest }) => ({ value: name, label: name, ...rest })) - return shaped - } catch (error) { - handleError(error) - return [] - } + ...isPublic, + }, + select: { + id: true, + name: true, + slug: true, + }, + }) + const shaped = orgIds.map(({ name, ...rest }) => ({ value: name, label: name, ...rest })) + return shaped } diff --git a/packages/api/router/organization/query.slugRedirect.handler.ts b/packages/api/router/organization/query.slugRedirect.handler.ts index 7ba9c7ed06..e8fbe06045 100644 --- a/packages/api/router/organization/query.slugRedirect.handler.ts +++ b/packages/api/router/organization/query.slugRedirect.handler.ts @@ -1,27 +1,22 @@ import { prisma } from '@weareinreach/db' import { readSlugRedirectCache, writeSlugRedirectCache } from '~api/cache/slugRedirect' -import { handleError } from '~api/lib/errorHandler' import { isPublic } from '~api/schemas/selects/common' import { type TRPCHandlerParams } from '~api/types/handler' import { type TSlugRedirectSchema } from './query.slugRedirect.schema' export const slugRedirect = async ({ input }: TRPCHandlerParams) => { - try { - const cached = await readSlugRedirectCache(input) - if (cached) { - return { redirectTo: cached } - } - const { slug: primarySlug } = await prisma.organization.findFirstOrThrow({ - where: { OR: [{ slug: input }, { oldSlugs: { some: { from: input } } }], ...isPublic }, - select: { slug: true }, - }) - if (primarySlug !== input) { - await writeSlugRedirectCache(input, primarySlug) - return { redirectTo: primarySlug } - } - return { redirectTo: null } - } catch (error) { - handleError(error) + const cached = await readSlugRedirectCache(input) + if (cached) { + return { redirectTo: cached } } + const { slug: primarySlug } = await prisma.organization.findFirstOrThrow({ + where: { OR: [{ slug: input }, { oldSlugs: { some: { from: input } } }], ...isPublic }, + select: { slug: true }, + }) + if (primarySlug !== input) { + await writeSlugRedirectCache(input, primarySlug) + return { redirectTo: primarySlug } + } + return { redirectTo: null } } diff --git a/packages/api/router/organization/query.suggestionOptions.handler.ts b/packages/api/router/organization/query.suggestionOptions.handler.ts index 24494cf736..ed1aee9607 100644 --- a/packages/api/router/organization/query.suggestionOptions.handler.ts +++ b/packages/api/router/organization/query.suggestionOptions.handler.ts @@ -1,52 +1,47 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' export const suggestionOptions = async () => { - try { - const [countries, serviceTypes, communities] = await Promise.all([ - prisma.country.findMany({ - where: { activeForSuggest: true }, - select: { id: true, tsKey: true, cca2: true }, - orderBy: { tsKey: 'desc' }, - }), - prisma.serviceCategory.findMany({ - where: { active: true, activeForSuggest: true }, - select: { id: true, tsKey: true, tsNs: true }, - orderBy: { tsKey: 'asc' }, - }), - prisma.attribute.findMany({ - where: { - categories: { some: { category: { tag: 'service-focus' } } }, - parents: { none: {} }, - activeForSuggest: true, - }, - select: { - id: true, - tsNs: true, - tsKey: true, - icon: true, - children: { - select: { - child: { select: { id: true, tsNs: true, tsKey: true } }, - }, + const [countries, serviceTypes, communities] = await Promise.all([ + prisma.country.findMany({ + where: { activeForSuggest: true }, + select: { id: true, tsKey: true, cca2: true }, + orderBy: { tsKey: 'desc' }, + }), + prisma.serviceCategory.findMany({ + where: { active: true, activeForSuggest: true }, + select: { id: true, tsKey: true, tsNs: true }, + orderBy: { tsKey: 'asc' }, + }), + prisma.attribute.findMany({ + where: { + categories: { some: { category: { tag: 'service-focus' } } }, + parents: { none: {} }, + activeForSuggest: true, + }, + select: { + id: true, + tsNs: true, + tsKey: true, + icon: true, + children: { + select: { + child: { select: { id: true, tsNs: true, tsKey: true } }, }, }, - orderBy: { tsKey: 'asc' }, - }), - ]) + }, + orderBy: { tsKey: 'asc' }, + }), + ]) - return { - countries, - serviceTypes, - communities: communities.map(({ children, ...record }) => { - const newChildren = children.map(({ child }) => ({ - ...child, - parentId: record.id, - })) - return { ...record, children: newChildren } - }), - } - } catch (error) { - handleError(error) + return { + countries, + serviceTypes, + communities: communities.map(({ children, ...record }) => { + const newChildren = children.map(({ child }) => ({ + ...child, + parentId: record.id, + })) + return { ...record, children: newChildren } + }), } } diff --git a/packages/api/router/quicklink/mutation.updateEmailData.handler.ts b/packages/api/router/quicklink/mutation.updateEmailData.handler.ts index 8fbf539023..57ca2b6a05 100644 --- a/packages/api/router/quicklink/mutation.updateEmailData.handler.ts +++ b/packages/api/router/quicklink/mutation.updateEmailData.handler.ts @@ -1,7 +1,6 @@ import flush from 'just-flush' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' @@ -11,35 +10,31 @@ export const updateEmailData = async ({ ctx, input, }: TRPCHandlerParams) => { - try { - const updates = input.map(({ id, from, to }) => { - const { serviceOnly, locationOnly, locations, services, published } = to - const auditLogs = CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from, to: flush(to) }) - return prisma.orgEmail.update({ - where: { id }, - data: { - serviceOnly, - locationOnly, - published, - locations: { - createMany: locations.add?.length - ? { data: locations.add.map((orgLocationId) => ({ orgLocationId })), skipDuplicates: true } - : undefined, - deleteMany: locations.del?.length ? { orgLocationId: { in: locations.del } } : undefined, - }, - services: { - createMany: services.add?.length - ? { data: services.add.map((serviceId) => ({ serviceId })), skipDuplicates: true } - : undefined, - deleteMany: services.del?.length ? { serviceId: { in: services.del } } : undefined, - }, - auditLogs, + const updates = input.map(({ id, from, to }) => { + const { serviceOnly, locationOnly, locations, services, published } = to + const auditLogs = CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from, to: flush(to) }) + return prisma.orgEmail.update({ + where: { id }, + data: { + serviceOnly, + locationOnly, + published, + locations: { + createMany: locations.add?.length + ? { data: locations.add.map((orgLocationId) => ({ orgLocationId })), skipDuplicates: true } + : undefined, + deleteMany: locations.del?.length ? { orgLocationId: { in: locations.del } } : undefined, }, - }) + services: { + createMany: services.add?.length + ? { data: services.add.map((serviceId) => ({ serviceId })), skipDuplicates: true } + : undefined, + deleteMany: services.del?.length ? { serviceId: { in: services.del } } : undefined, + }, + auditLogs, + }, }) - const results = await prisma.$transaction(updates) - return results - } catch (error) { - handleError(error) - } + }) + const results = await prisma.$transaction(updates) + return results } diff --git a/packages/api/router/quicklink/mutation.updatePhoneData.handler.ts b/packages/api/router/quicklink/mutation.updatePhoneData.handler.ts index a7a582eb82..84f95748dc 100644 --- a/packages/api/router/quicklink/mutation.updatePhoneData.handler.ts +++ b/packages/api/router/quicklink/mutation.updatePhoneData.handler.ts @@ -1,7 +1,6 @@ import flush from 'just-flush' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' @@ -11,35 +10,31 @@ export const updatePhoneData = async ({ ctx, input, }: TRPCHandlerParams) => { - try { - const updates = input.map(({ id, from, to }) => { - const { serviceOnly, locationOnly, locations, services, published } = to - const auditLogs = CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from, to: flush(to) }) - return prisma.orgPhone.update({ - where: { id }, - data: { - serviceOnly, - locationOnly, - published, - locations: { - createMany: locations.add?.length - ? { data: locations.add.map((orgLocationId) => ({ orgLocationId })), skipDuplicates: true } - : undefined, - deleteMany: locations.del?.length ? { orgLocationId: { in: locations.del } } : undefined, - }, - services: { - createMany: services.add?.length - ? { data: services.add.map((serviceId) => ({ serviceId })), skipDuplicates: true } - : undefined, - deleteMany: services.del?.length ? { serviceId: { in: services.del } } : undefined, - }, - auditLogs, + const updates = input.map(({ id, from, to }) => { + const { serviceOnly, locationOnly, locations, services, published } = to + const auditLogs = CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from, to: flush(to) }) + return prisma.orgPhone.update({ + where: { id }, + data: { + serviceOnly, + locationOnly, + published, + locations: { + createMany: locations.add?.length + ? { data: locations.add.map((orgLocationId) => ({ orgLocationId })), skipDuplicates: true } + : undefined, + deleteMany: locations.del?.length ? { orgLocationId: { in: locations.del } } : undefined, }, - }) + services: { + createMany: services.add?.length + ? { data: services.add.map((serviceId) => ({ serviceId })), skipDuplicates: true } + : undefined, + deleteMany: services.del?.length ? { serviceId: { in: services.del } } : undefined, + }, + auditLogs, + }, }) - const results = await prisma.$transaction(updates) - return results - } catch (error) { - handleError(error) - } + }) + const results = await prisma.$transaction(updates) + return results } diff --git a/packages/api/router/quicklink/mutation.updateServiceLocationData.handler.ts b/packages/api/router/quicklink/mutation.updateServiceLocationData.handler.ts index 27d9001ed0..1cd0f65672 100644 --- a/packages/api/router/quicklink/mutation.updateServiceLocationData.handler.ts +++ b/packages/api/router/quicklink/mutation.updateServiceLocationData.handler.ts @@ -1,7 +1,6 @@ import flush from 'just-flush' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' @@ -11,27 +10,23 @@ export const updateServiceLocationData = async ({ ctx, input, }: TRPCHandlerParams) => { - try { - const updates = input.map(({ id, from, to }) => { - const { services, published } = to - const auditLogs = CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from, to: flush(to) }) - return prisma.orgLocation.update({ - where: { id }, - data: { - published, - services: { - createMany: services.add?.length - ? { data: services.add.map((serviceId) => ({ serviceId })), skipDuplicates: true } - : undefined, - deleteMany: services.del?.length ? { serviceId: { in: services.del } } : undefined, - }, - auditLogs, + const updates = input.map(({ id, from, to }) => { + const { services, published } = to + const auditLogs = CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from, to: flush(to) }) + return prisma.orgLocation.update({ + where: { id }, + data: { + published, + services: { + createMany: services.add?.length + ? { data: services.add.map((serviceId) => ({ serviceId })), skipDuplicates: true } + : undefined, + deleteMany: services.del?.length ? { serviceId: { in: services.del } } : undefined, }, - }) + auditLogs, + }, }) - const results = await prisma.$transaction(updates) - return results - } catch (error) { - handleError(error) - } + }) + const results = await prisma.$transaction(updates) + return results } diff --git a/packages/api/router/quicklink/query.getEmailData.handler.ts b/packages/api/router/quicklink/query.getEmailData.handler.ts index 29c840d0f4..acddfa3e43 100644 --- a/packages/api/router/quicklink/query.getEmailData.handler.ts +++ b/packages/api/router/quicklink/query.getEmailData.handler.ts @@ -1,89 +1,84 @@ import { type Prisma, prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetEmailDataSchema } from './query.getEmailData.schema' export const getEmailData = async ({ input }: TRPCHandlerParams) => { - try { - const limit = input.limit ?? 20 - const { skip } = input + const limit = input.limit ?? 20 + const { skip } = input - const where = { - published: true, - deleted: false, - emails: { - some: { - email: { AND: [{ locations: { none: {} } }, { services: { none: {} } }, { published: true }] }, - }, + const where = { + published: true, + deleted: false, + emails: { + some: { + email: { AND: [{ locations: { none: {} } }, { services: { none: {} } }, { published: true }] }, }, - } satisfies Prisma.OrganizationWhereInput + }, + } satisfies Prisma.OrganizationWhereInput - const data = await prisma.organization.findMany({ - where, - select: { - id: true, - name: true, - slug: true, - locations: { select: { id: true, name: true } }, - services: { - select: { - id: true, - serviceName: { select: { tsKey: { select: { text: true } } } }, - }, + const data = await prisma.organization.findMany({ + where, + select: { + id: true, + name: true, + slug: true, + locations: { select: { id: true, name: true } }, + services: { + select: { + id: true, + serviceName: { select: { tsKey: { select: { text: true } } } }, }, - emails: { - select: { - email: { - select: { - id: true, - email: true, - firstName: true, - lastName: true, - locationOnly: true, - serviceOnly: true, - published: true, - description: { select: { tsKey: { select: { text: true } } } }, - locations: { select: { location: { select: { id: true } } } }, - services: { select: { service: { select: { id: true } } } }, - }, + }, + emails: { + select: { + email: { + select: { + id: true, + email: true, + firstName: true, + lastName: true, + locationOnly: true, + serviceOnly: true, + published: true, + description: { select: { tsKey: { select: { text: true } } } }, + locations: { select: { location: { select: { id: true } } } }, + services: { select: { service: { select: { id: true } } } }, }, }, }, }, - orderBy: { id: 'asc' }, - take: limit, // + 1, - skip, + }, + orderBy: { id: 'asc' }, + take: limit, // + 1, + skip, + }) + const totalResults = await prisma.organization.count({ where }) + const transformedData = data.flatMap(({ locations, emails, services, id, name, slug }) => + emails.map(({ email }) => { + const { + description, + id: emailId, + locations: attachedLocations, + services: attachedServices, + ...rest + } = email + return { + orgId: id, + name, + slug, + emailId, + ...rest, + description: description?.tsKey.text, + attachedLocations: attachedLocations.map(({ location }) => location.id), + attachedServices: attachedServices.map(({ service }) => service.id), + locations, + services, + } }) - const totalResults = await prisma.organization.count({ where }) - const transformedData = data.flatMap(({ locations, emails, services, id, name, slug }) => - emails.map(({ email }) => { - const { - description, - id: emailId, - locations: attachedLocations, - services: attachedServices, - ...rest - } = email - return { - orgId: id, - name, - slug, - emailId, - ...rest, - description: description?.tsKey.text, - attachedLocations: attachedLocations.map(({ location }) => location.id), - attachedServices: attachedServices.map(({ service }) => service.id), - locations, - services, - } - }) - ) - return { - results: transformedData, - totalResults, - } - } catch (error) { - handleError(error) + ) + return { + results: transformedData, + totalResults, } } diff --git a/packages/api/router/quicklink/query.getPhoneData.handler.ts b/packages/api/router/quicklink/query.getPhoneData.handler.ts index 939642cd70..712d9a4950 100644 --- a/packages/api/router/quicklink/query.getPhoneData.handler.ts +++ b/packages/api/router/quicklink/query.getPhoneData.handler.ts @@ -1,95 +1,90 @@ import { type Prisma, prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetPhoneDataSchema } from './query.getPhoneData.schema' export const getPhoneData = async ({ input }: TRPCHandlerParams) => { - try { - const limit = input.limit ?? 20 - const { skip } = input + const limit = input.limit ?? 20 + const { skip } = input - const where = { - published: true, - deleted: false, - phones: { - some: { - phone: { AND: [{ locations: { none: {} } }, { services: { none: {} } }, { published: true }] }, - }, + const where = { + published: true, + deleted: false, + phones: { + some: { + phone: { AND: [{ locations: { none: {} } }, { services: { none: {} } }, { published: true }] }, }, - } satisfies Prisma.OrganizationWhereInput + }, + } satisfies Prisma.OrganizationWhereInput - const data = await prisma.organization.findMany({ - where, - select: { - id: true, - name: true, - slug: true, - locations: { - select: { - id: true, - name: true, - // phones: { select: { phone: { select: { id: true } } } }, - }, + const data = await prisma.organization.findMany({ + where, + select: { + id: true, + name: true, + slug: true, + locations: { + select: { + id: true, + name: true, + // phones: { select: { phone: { select: { id: true } } } }, }, - phones: { - select: { - phone: { - select: { - id: true, - number: true, - description: { select: { tsKey: { select: { text: true } } } }, - country: { select: { id: true, cca2: true } }, - locationOnly: true, - serviceOnly: true, - locations: { select: { location: { select: { id: true } } } }, - services: { select: { service: { select: { id: true } } } }, - published: true, - }, + }, + phones: { + select: { + phone: { + select: { + id: true, + number: true, + description: { select: { tsKey: { select: { text: true } } } }, + country: { select: { id: true, cca2: true } }, + locationOnly: true, + serviceOnly: true, + locations: { select: { location: { select: { id: true } } } }, + services: { select: { service: { select: { id: true } } } }, + published: true, }, }, }, - services: { - select: { - id: true, - serviceName: { select: { tsKey: { select: { text: true } } } }, - // phones: { select: { phone: { select: { id: true } } } }, - }, + }, + services: { + select: { + id: true, + serviceName: { select: { tsKey: { select: { text: true } } } }, + // phones: { select: { phone: { select: { id: true } } } }, }, }, - orderBy: { id: 'asc' }, - take: limit, // + 1, - skip, + }, + orderBy: { id: 'asc' }, + take: limit, // + 1, + skip, + }) + const totalResults = await prisma.organization.count({ where }) + const transformedData = data.flatMap(({ locations, phones, services, id, name, slug }) => + phones.map(({ phone }) => { + const { + description, + id: phoneId, + locations: attachedLocations, + services: attachedServices, + ...rest + } = phone + return { + orgId: id, + name, + slug, + phoneId, + ...rest, + description: description?.tsKey.text, + attachedLocations: attachedLocations.map(({ location }) => location.id), + attachedServices: attachedServices.map(({ service }) => service.id), + locations, + services, + } }) - const totalResults = await prisma.organization.count({ where }) - const transformedData = data.flatMap(({ locations, phones, services, id, name, slug }) => - phones.map(({ phone }) => { - const { - description, - id: phoneId, - locations: attachedLocations, - services: attachedServices, - ...rest - } = phone - return { - orgId: id, - name, - slug, - phoneId, - ...rest, - description: description?.tsKey.text, - attachedLocations: attachedLocations.map(({ location }) => location.id), - attachedServices: attachedServices.map(({ service }) => service.id), - locations, - services, - } - }) - ) - return { - results: transformedData, - totalResults, - } - } catch (error) { - handleError(error) + ) + return { + results: transformedData, + totalResults, } } diff --git a/packages/api/router/quicklink/query.getServiceLocationData.handler.ts b/packages/api/router/quicklink/query.getServiceLocationData.handler.ts index 584a54d443..b44ee0d6b1 100644 --- a/packages/api/router/quicklink/query.getServiceLocationData.handler.ts +++ b/packages/api/router/quicklink/query.getServiceLocationData.handler.ts @@ -1,73 +1,68 @@ import { type Prisma, prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetServiceLocationDataSchema } from './query.getServiceLocationData.schema' export const getServiceLocationData = async ({ input }: TRPCHandlerParams) => { - try { - const limit = input.limit ?? 20 - const { skip } = input + const limit = input.limit ?? 20 + const { skip } = input - const where = { - published: true, - deleted: false, + const where = { + published: true, + deleted: false, + locations: { + some: { services: { none: {} }, published: true }, + }, + } satisfies Prisma.OrganizationWhereInput + const results = await prisma.organization.findMany({ + where, + select: { + id: true, + name: true, + slug: true, locations: { - some: { services: { none: {} }, published: true }, - }, - } satisfies Prisma.OrganizationWhereInput - const results = await prisma.organization.findMany({ - where, - select: { - id: true, - name: true, - slug: true, - locations: { - select: { - id: true, - name: true, - published: true, - services: { - select: { - service: { - select: { id: true, serviceName: { select: { tsKey: { select: { text: true } } } } }, - }, + select: { + id: true, + name: true, + published: true, + services: { + select: { + service: { + select: { id: true, serviceName: { select: { tsKey: { select: { text: true } } } } }, }, }, }, - where: { services: { none: {} }, published: true, deleted: false }, }, - services: { - select: { - id: true, - serviceName: { select: { tsKey: { select: { text: true } } } }, - }, + where: { services: { none: {} }, published: true, deleted: false }, + }, + services: { + select: { + id: true, + serviceName: { select: { tsKey: { select: { text: true } } } }, }, }, - orderBy: { id: 'asc' }, - take: limit, // + 1, - skip, + }, + orderBy: { id: 'asc' }, + take: limit, // + 1, + skip, + }) + const totalResults = await prisma.organization.count({ where }) + const transformedData = results.flatMap(({ locations, services, id, name, slug }) => + locations.map(({ id: locationId, name: locationName, services: locationServices, published }) => { + return { + orgId: id, + name, + slug, + locationId, + locationName, + published, + attachedServices: locationServices.map(({ service }) => service.id), + services, + } }) - const totalResults = await prisma.organization.count({ where }) - const transformedData = results.flatMap(({ locations, services, id, name, slug }) => - locations.map(({ id: locationId, name: locationName, services: locationServices, published }) => { - return { - orgId: id, - name, - slug, - locationId, - locationName, - published, - attachedServices: locationServices.map(({ service }) => service.id), - services, - } - }) - ) - return { - results: transformedData, - totalResults, - } - } catch (error) { - handleError(error) + ) + return { + results: transformedData, + totalResults, } } diff --git a/packages/api/router/review/mutation.create.handler.ts b/packages/api/router/review/mutation.create.handler.ts index 1d12af0bac..da4bb16649 100644 --- a/packages/api/router/review/mutation.create.handler.ts +++ b/packages/api/router/review/mutation.create.handler.ts @@ -1,27 +1,22 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateSchema } from './mutation.create.schema' export const create = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const review = await prisma.orgReview.create({ - data: { - ...input, - userId: ctx.session.user.id, - auditLogs: CreateAuditLog({ - actorId: ctx.actorId, - operation: 'CREATE', - to: { ...input, userId: ctx.session.user.id }, - }), - }, - select: { id: true }, - }) + const review = await prisma.orgReview.create({ + data: { + ...input, + userId: ctx.session.user.id, + auditLogs: CreateAuditLog({ + actorId: ctx.actorId, + operation: 'CREATE', + to: { ...input, userId: ctx.session.user.id }, + }), + }, + select: { id: true }, + }) - return review - } catch (error) { - handleError(error) - } + return review } diff --git a/packages/api/router/review/mutation.delete.handler.ts b/packages/api/router/review/mutation.delete.handler.ts index 0c5e1ddd8d..6b3e3040b7 100644 --- a/packages/api/router/review/mutation.delete.handler.ts +++ b/packages/api/router/review/mutation.delete.handler.ts @@ -1,32 +1,27 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TDeleteSchema } from './mutation.delete.schema' export const deleteReview = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const deleted = true + const deleted = true - const result = await prisma.orgReview.update({ - where: { id: input.id }, - data: { - deleted, - auditLogs: CreateAuditLog({ - actorId: ctx.actorId, - operation: 'UPDATE', - from: { deleted: !deleted }, - to: { deleted: deleted }, - }), - }, - select: { - id: true, - deleted: true, - }, - }) - return result - } catch (error) { - handleError(error) - } + const result = await prisma.orgReview.update({ + where: { id: input.id }, + data: { + deleted, + auditLogs: CreateAuditLog({ + actorId: ctx.actorId, + operation: 'UPDATE', + from: { deleted: !deleted }, + to: { deleted: deleted }, + }), + }, + select: { + id: true, + deleted: true, + }, + }) + return result } diff --git a/packages/api/router/review/mutation.hide.handler.ts b/packages/api/router/review/mutation.hide.handler.ts index c356adb3f0..0fd891ed5b 100644 --- a/packages/api/router/review/mutation.hide.handler.ts +++ b/packages/api/router/review/mutation.hide.handler.ts @@ -1,32 +1,27 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type THideSchema } from './mutation.hide.schema' export const hide = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const visible = false + const visible = false - const result = await prisma.orgReview.update({ - where: { id: input.id }, - data: { - visible, - auditLogs: CreateAuditLog({ - actorId: ctx.actorId, - operation: 'UPDATE', - from: { visible: !visible }, - to: { visible }, - }), - }, - select: { - id: true, - visible: true, - }, - }) - return result - } catch (error) { - handleError(error) - } + const result = await prisma.orgReview.update({ + where: { id: input.id }, + data: { + visible, + auditLogs: CreateAuditLog({ + actorId: ctx.actorId, + operation: 'UPDATE', + from: { visible: !visible }, + to: { visible }, + }), + }, + select: { + id: true, + visible: true, + }, + }) + return result } diff --git a/packages/api/router/review/mutation.unDelete.handler.ts b/packages/api/router/review/mutation.unDelete.handler.ts index e2a7800ea0..3c46e5829f 100644 --- a/packages/api/router/review/mutation.unDelete.handler.ts +++ b/packages/api/router/review/mutation.unDelete.handler.ts @@ -1,32 +1,27 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TUnDeleteSchema } from './mutation.unDelete.schema' export const unDelete = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const deleted = false + const deleted = false - const result = await prisma.orgReview.update({ - where: { id: input.id }, - data: { - deleted, - auditLogs: CreateAuditLog({ - actorId: ctx.actorId, - operation: 'UPDATE', - from: { deleted: !deleted }, - to: { deleted: deleted }, - }), - }, - select: { - id: true, - deleted: true, - }, - }) - return result - } catch (error) { - handleError(error) - } + const result = await prisma.orgReview.update({ + where: { id: input.id }, + data: { + deleted, + auditLogs: CreateAuditLog({ + actorId: ctx.actorId, + operation: 'UPDATE', + from: { deleted: !deleted }, + to: { deleted: deleted }, + }), + }, + select: { + id: true, + deleted: true, + }, + }) + return result } diff --git a/packages/api/router/review/mutation.unHide.handler.ts b/packages/api/router/review/mutation.unHide.handler.ts index 25dca49ce9..7a8aa0880a 100644 --- a/packages/api/router/review/mutation.unHide.handler.ts +++ b/packages/api/router/review/mutation.unHide.handler.ts @@ -1,32 +1,27 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TUnHideSchema } from './mutation.unHide.schema' export const unHide = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const visible = true + const visible = true - const result = await prisma.orgReview.update({ - where: { id: input.id }, - data: { - visible, - auditLogs: CreateAuditLog({ - actorId: ctx.actorId, - operation: 'UPDATE', - from: { visible: !visible }, - to: { visible }, - }), - }, - select: { - id: true, - visible: true, - }, - }) - return result - } catch (error) { - handleError(error) - } + const result = await prisma.orgReview.update({ + where: { id: input.id }, + data: { + visible, + auditLogs: CreateAuditLog({ + actorId: ctx.actorId, + operation: 'UPDATE', + from: { visible: !visible }, + to: { visible }, + }), + }, + select: { + id: true, + visible: true, + }, + }) + return result } diff --git a/packages/api/router/review/query.getAverage.handler.ts b/packages/api/router/review/query.getAverage.handler.ts index 37334e64d7..821708cfd0 100644 --- a/packages/api/router/review/query.getAverage.handler.ts +++ b/packages/api/router/review/query.getAverage.handler.ts @@ -1,39 +1,34 @@ import { isIdFor, type Prisma, prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetAverageSchema } from './query.getAverage.schema' export const getAverage = async ({ input }: TRPCHandlerParams) => { - try { - const whereId = (): Prisma.OrgReviewWhereInput => { - switch (true) { - case isIdFor('organization', input): { - return { organizationId: input } - } - case isIdFor('orgLocation', input): { - return { orgLocationId: input } - } - case isIdFor('orgService', input): { - return { orgServiceId: input } - } - default: { - return {} - } + const whereId = (): Prisma.OrgReviewWhereInput => { + switch (true) { + case isIdFor('organization', input): { + return { organizationId: input } + } + case isIdFor('orgLocation', input): { + return { orgLocationId: input } + } + case isIdFor('orgService', input): { + return { orgServiceId: input } + } + default: { + return {} } } - - const result = await prisma.orgReview.aggregate({ - _avg: { - rating: true, - }, - _count: { - rating: true, - }, - where: whereId(), - }) - return { average: result._avg.rating, count: result._count.rating } - } catch (error) { - handleError(error) } + + const result = await prisma.orgReview.aggregate({ + _avg: { + rating: true, + }, + _count: { + rating: true, + }, + where: whereId(), + }) + return { average: result._avg.rating, count: result._count.rating } } diff --git a/packages/api/router/review/query.getByIds.handler.ts b/packages/api/router/review/query.getByIds.handler.ts index c8cf297b80..9e65ad1ab9 100644 --- a/packages/api/router/review/query.getByIds.handler.ts +++ b/packages/api/router/review/query.getByIds.handler.ts @@ -1,106 +1,101 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetByIdsSchema } from './query.getByIds.schema' export const getByIds = async ({ input }: TRPCHandlerParams) => { - try { - const results = await prisma.orgReview.findMany({ - where: { - id: { - in: input, - }, - visible: true, - deleted: false, + const results = await prisma.orgReview.findMany({ + where: { + id: { + in: input, }, - select: { - id: true, - rating: true, - reviewText: true, - user: { - select: { - name: true, - image: true, - fieldVisibility: { - select: { - name: true, - image: true, - currentCity: true, - currentGovDist: true, - currentCountry: true, - }, + visible: true, + deleted: false, + }, + select: { + id: true, + rating: true, + reviewText: true, + user: { + select: { + name: true, + image: true, + fieldVisibility: { + select: { + name: true, + image: true, + currentCity: true, + currentGovDist: true, + currentCountry: true, }, - permissions: { - where: { - permission: { - name: 'isLCR', - }, + }, + permissions: { + where: { + permission: { + name: 'isLCR', }, }, }, }, - language: { - select: { - languageName: true, - nativeName: true, - }, + }, + language: { + select: { + languageName: true, + nativeName: true, }, - langConfidence: true, - translatedText: { - select: { - text: true, - language: { - select: { localeCode: true }, - }, + }, + langConfidence: true, + translatedText: { + select: { + text: true, + language: { + select: { localeCode: true }, }, }, - lcrCity: true, - lcrGovDist: { - select: { - tsKey: true, - tsNs: true, - }, + }, + lcrCity: true, + lcrGovDist: { + select: { + tsKey: true, + tsNs: true, }, - lcrCountry: { - select: { - tsNs: true, - tsKey: true, - }, + }, + lcrCountry: { + select: { + tsNs: true, + tsKey: true, }, - createdAt: true, }, - }) + createdAt: true, + }, + }) - const filteredResults = results.map((result) => { - const { - name: nameVisible, - image: imageVisible, - currentCity: cityVisible, - currentGovDist: distVisible, - currentCountry: countryVisible, - } = result.user.fieldVisibility ?? { - name: false, - image: false, - currentCity: false, - currentGovDist: false, - currentCountry: false, - } + const filteredResults = results.map((result) => { + const { + name: nameVisible, + image: imageVisible, + currentCity: cityVisible, + currentGovDist: distVisible, + currentCountry: countryVisible, + } = result.user.fieldVisibility ?? { + name: false, + image: false, + currentCity: false, + currentGovDist: false, + currentCountry: false, + } - return { - ...result, - user: { - image: imageVisible ? result.user.image : null, - name: nameVisible ? result.user.name : null, - }, - lcrCity: cityVisible ? result.lcrCity : null, - lcrGovDist: distVisible ? result.lcrGovDist : null, - lcrCountry: countryVisible ? result.lcrCountry : null, - verifiedUser: Boolean(result.user.permissions.length), - } - }) - return filteredResults - } catch (error) { - handleError(error) - } + return { + ...result, + user: { + image: imageVisible ? result.user.image : null, + name: nameVisible ? result.user.name : null, + }, + lcrCity: cityVisible ? result.lcrCity : null, + lcrGovDist: distVisible ? result.lcrGovDist : null, + lcrCountry: countryVisible ? result.lcrCountry : null, + verifiedUser: Boolean(result.user.permissions.length), + } + }) + return filteredResults } diff --git a/packages/api/router/review/query.getByLocation.handler.ts b/packages/api/router/review/query.getByLocation.handler.ts index 1d50a4b869..ef29e9feda 100644 --- a/packages/api/router/review/query.getByLocation.handler.ts +++ b/packages/api/router/review/query.getByLocation.handler.ts @@ -1,19 +1,14 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetByLocationSchema } from './query.getByLocation.schema' export const getByLocation = async ({ input }: TRPCHandlerParams) => { - try { - const reviews = await prisma.orgReview.findMany({ - where: { - organizationId: input.orgId, - orgLocationId: input.locationId, - }, - }) - return reviews - } catch (error) { - handleError(error) - } + const reviews = await prisma.orgReview.findMany({ + where: { + organizationId: input.orgId, + orgLocationId: input.locationId, + }, + }) + return reviews } diff --git a/packages/api/router/review/query.getByOrg.handler.ts b/packages/api/router/review/query.getByOrg.handler.ts index a7c1ebc788..1545237284 100644 --- a/packages/api/router/review/query.getByOrg.handler.ts +++ b/packages/api/router/review/query.getByOrg.handler.ts @@ -1,18 +1,13 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetByOrgSchema } from './query.getByOrg.schema' export const getByOrg = async ({ input }: TRPCHandlerParams) => { - try { - const reviews = await prisma.orgReview.findMany({ - where: { - organizationId: input.orgId, - }, - }) - return reviews - } catch (error) { - handleError(error) - } + const reviews = await prisma.orgReview.findMany({ + where: { + organizationId: input.orgId, + }, + }) + return reviews } diff --git a/packages/api/router/review/query.getByService.handler.ts b/packages/api/router/review/query.getByService.handler.ts index 92d94d654d..62adc4939a 100644 --- a/packages/api/router/review/query.getByService.handler.ts +++ b/packages/api/router/review/query.getByService.handler.ts @@ -1,19 +1,14 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetByServiceSchema } from './query.getByService.schema' export const getByService = async ({ input }: TRPCHandlerParams) => { - try { - const reviews = await prisma.orgReview.findMany({ - where: { - organizationId: input.orgId, - orgServiceId: input.serviceId, - }, - }) - return reviews - } catch (error) { - handleError(error) - } + const reviews = await prisma.orgReview.findMany({ + where: { + organizationId: input.orgId, + orgServiceId: input.serviceId, + }, + }) + return reviews } diff --git a/packages/api/router/review/query.getByUser.handler.ts b/packages/api/router/review/query.getByUser.handler.ts index 3ff6a13988..fd4c0314fa 100644 --- a/packages/api/router/review/query.getByUser.handler.ts +++ b/packages/api/router/review/query.getByUser.handler.ts @@ -1,23 +1,18 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetByUserSchema } from './query.getByUser.schema' export const getByUser = async ({ input }: TRPCHandlerParams) => { - try { - const reviews = await prisma.orgReview.findMany({ - where: { - userId: input.userId, - }, - include: { - organization: true, - orgLocation: true, - orgService: true, - }, - }) - return reviews - } catch (error) { - handleError(error) - } + const reviews = await prisma.orgReview.findMany({ + where: { + userId: input.userId, + }, + include: { + organization: true, + orgLocation: true, + orgService: true, + }, + }) + return reviews } diff --git a/packages/api/router/review/query.getCurrentUser.handler.ts b/packages/api/router/review/query.getCurrentUser.handler.ts index 9e8f314f18..be04f8f230 100644 --- a/packages/api/router/review/query.getCurrentUser.handler.ts +++ b/packages/api/router/review/query.getCurrentUser.handler.ts @@ -1,21 +1,16 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' export const getCurrentUser = async ({ ctx }: TRPCHandlerParams) => { - try { - const reviews = await prisma.orgReview.findMany({ - where: { - userId: ctx.session.user.id, - }, - include: { - organization: true, - orgLocation: true, - orgService: true, - }, - }) - return reviews - } catch (error) { - handleError(error) - } + const reviews = await prisma.orgReview.findMany({ + where: { + userId: ctx.session.user.id, + }, + include: { + organization: true, + orgLocation: true, + orgService: true, + }, + }) + return reviews } diff --git a/packages/api/router/review/query.getFeatured.handler.ts b/packages/api/router/review/query.getFeatured.handler.ts index 4c8e8c1b20..0451077733 100644 --- a/packages/api/router/review/query.getFeatured.handler.ts +++ b/packages/api/router/review/query.getFeatured.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetFeaturedSchema } from './query.getFeatured.schema' @@ -17,102 +16,98 @@ const getRandomItems = (items: T[], count: number): T[] => { } export const getFeatured = async ({ input }: TRPCHandlerParams) => { - try { - const results = await prisma.orgReview.findMany({ - where: { - featured: true, - visible: true, - deleted: false, - }, - select: { - id: true, - rating: true, - reviewText: true, - user: { - select: { - name: true, - image: true, - fieldVisibility: { - select: { - name: true, - image: true, - currentCity: true, - currentGovDist: true, - currentCountry: true, - }, + const results = await prisma.orgReview.findMany({ + where: { + featured: true, + visible: true, + deleted: false, + }, + select: { + id: true, + rating: true, + reviewText: true, + user: { + select: { + name: true, + image: true, + fieldVisibility: { + select: { + name: true, + image: true, + currentCity: true, + currentGovDist: true, + currentCountry: true, }, - permissions: { - where: { - permission: { - name: 'isLCR', - }, + }, + permissions: { + where: { + permission: { + name: 'isLCR', }, }, }, }, - language: { - select: { - languageName: true, - nativeName: true, - }, + }, + language: { + select: { + languageName: true, + nativeName: true, }, - langConfidence: true, - translatedText: { - select: { - text: true, - language: { - select: { localeCode: true }, - }, + }, + langConfidence: true, + translatedText: { + select: { + text: true, + language: { + select: { localeCode: true }, }, }, - lcrCity: true, - lcrGovDist: { - select: { - tsKey: true, - tsNs: true, - }, + }, + lcrCity: true, + lcrGovDist: { + select: { + tsKey: true, + tsNs: true, }, - lcrCountry: { - select: { - tsNs: true, - tsKey: true, - }, + }, + lcrCountry: { + select: { + tsNs: true, + tsKey: true, }, - createdAt: true, }, - }) + createdAt: true, + }, + }) - const randomResults = getRandomItems(results, input) + const randomResults = getRandomItems(results, input) - const filteredResults = randomResults.map((result) => { - const { - name: nameVisible, - image: imageVisible, - currentCity: cityVisible, - currentGovDist: distVisible, - currentCountry: countryVisible, - } = result.user.fieldVisibility ?? { - name: false, - image: false, - currentCity: false, - currentGovDist: false, - currentCountry: false, - } + const filteredResults = randomResults.map((result) => { + const { + name: nameVisible, + image: imageVisible, + currentCity: cityVisible, + currentGovDist: distVisible, + currentCountry: countryVisible, + } = result.user.fieldVisibility ?? { + name: false, + image: false, + currentCity: false, + currentGovDist: false, + currentCountry: false, + } - return { - ...result, - user: { - image: imageVisible ? result.user.image : null, - name: nameVisible ? result.user.name : null, - }, - lcrCity: cityVisible ? result.lcrCity : null, - lcrGovDist: distVisible ? result.lcrGovDist : null, - lcrCountry: countryVisible ? result.lcrCountry : null, - verifiedUser: Boolean(result.user.permissions.length), - } - }) - return filteredResults - } catch (error) { - handleError(error) - } + return { + ...result, + user: { + image: imageVisible ? result.user.image : null, + name: nameVisible ? result.user.name : null, + }, + lcrCity: cityVisible ? result.lcrCity : null, + lcrGovDist: distVisible ? result.lcrGovDist : null, + lcrCountry: countryVisible ? result.lcrCountry : null, + verifiedUser: Boolean(result.user.permissions.length), + } + }) + return filteredResults } diff --git a/packages/api/router/savedLists/mutation.create.handler.ts b/packages/api/router/savedLists/mutation.create.handler.ts index c40e056e3f..7442c6fed1 100644 --- a/packages/api/router/savedLists/mutation.create.handler.ts +++ b/packages/api/router/savedLists/mutation.create.handler.ts @@ -1,27 +1,22 @@ import { type z } from 'zod' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateSchema, ZCreateSchema } from './mutation.create.schema' export const create = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { dataParser } = ZCreateSchema() + const { dataParser } = ZCreateSchema() - const inputData = { - actorId: ctx.session.user.id, - ownedById: ctx.session.user.id, - data: input, - operation: 'CREATE', - } satisfies z.input + const inputData = { + actorId: ctx.session.user.id, + ownedById: ctx.session.user.id, + data: input, + operation: 'CREATE', + } satisfies z.input - const data = dataParser.parse(inputData) + const data = dataParser.parse(inputData) - const list = await prisma.userSavedList.create(data) - return list - } catch (error) { - handleError(error) - } + const list = await prisma.userSavedList.create(data) + return list } diff --git a/packages/api/router/savedLists/mutation.createAndSaveItem.handler.ts b/packages/api/router/savedLists/mutation.createAndSaveItem.handler.ts index 47d1213945..a0320a3f47 100644 --- a/packages/api/router/savedLists/mutation.createAndSaveItem.handler.ts +++ b/packages/api/router/savedLists/mutation.createAndSaveItem.handler.ts @@ -1,7 +1,6 @@ import { type z } from 'zod' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateAndSaveItemSchema, ZCreateAndSaveItemSchema } from './mutation.createAndSaveItem.schema' @@ -10,26 +9,22 @@ export const createAndSaveItem = async ({ ctx, input, }: TRPCHandlerParams) => { - try { - const { dataParser } = ZCreateAndSaveItemSchema() + const { dataParser } = ZCreateAndSaveItemSchema() - const inputData = { - actorId: ctx.session.user.id, - operation: 'CREATE', - ownedById: ctx.session.user.id, - data: input, - } satisfies z.input + const inputData = { + actorId: ctx.session.user.id, + operation: 'CREATE', + ownedById: ctx.session.user.id, + data: input, + } satisfies z.input - const data = dataParser.parse(inputData) - const result = await prisma.userSavedList.create(data) + const data = dataParser.parse(inputData) + const result = await prisma.userSavedList.create(data) - const flattenedResult = { - ...result, - organizations: result.organizations.map((x) => x.organizationId), - services: result.services.map((x) => x.serviceId), - } - return flattenedResult - } catch (error) { - handleError(error) + const flattenedResult = { + ...result, + organizations: result.organizations.map((x) => x.organizationId), + services: result.services.map((x) => x.serviceId), } + return flattenedResult } diff --git a/packages/api/router/savedLists/mutation.delete.handler.ts b/packages/api/router/savedLists/mutation.delete.handler.ts index ec54e6dc5c..d9b1869c33 100644 --- a/packages/api/router/savedLists/mutation.delete.handler.ts +++ b/packages/api/router/savedLists/mutation.delete.handler.ts @@ -1,35 +1,30 @@ import { TRPCError } from '@trpc/server' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TDeleteSchema } from './mutation.delete.schema' export const deleteList = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const list = await prisma.userSavedList.findUniqueOrThrow({ - where: { - id: input.id, - }, - select: { id: true, ownedById: true }, - }) + const list = await prisma.userSavedList.findUniqueOrThrow({ + where: { + id: input.id, + }, + select: { id: true, ownedById: true }, + }) - if (list.ownedById !== ctx.session.user.id) { - throw new TRPCError({ code: 'UNAUTHORIZED', message: 'List does not belong to user' }) - } - - const result = await prisma.userSavedList.delete({ - where: { - id: input.id, - }, - select: { - id: true, - name: true, - }, - }) - return result - } catch (error) { - handleError(error) + if (list.ownedById !== ctx.session.user.id) { + throw new TRPCError({ code: 'UNAUTHORIZED', message: 'List does not belong to user' }) } + + const result = await prisma.userSavedList.delete({ + where: { + id: input.id, + }, + select: { + id: true, + name: true, + }, + }) + return result } diff --git a/packages/api/router/savedLists/mutation.deleteItem.handler.ts b/packages/api/router/savedLists/mutation.deleteItem.handler.ts index f78c4dd50d..536190a861 100644 --- a/packages/api/router/savedLists/mutation.deleteItem.handler.ts +++ b/packages/api/router/savedLists/mutation.deleteItem.handler.ts @@ -1,31 +1,26 @@ import { type z } from 'zod' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TDeleteItemSchema, ZDeleteItemSchema } from './mutation.deleteItem.schema' export const deleteItem = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { dataParser } = ZDeleteItemSchema() + const { dataParser } = ZDeleteItemSchema() - const inputData = { - actorId: ctx.session.user.id, - ownedById: ctx.session.user.id, - operation: 'UNLINK', - data: input, - } satisfies z.input - const data = dataParser.parse(inputData) + const inputData = { + actorId: ctx.session.user.id, + ownedById: ctx.session.user.id, + operation: 'UNLINK', + data: input, + } satisfies z.input + const data = dataParser.parse(inputData) - const result = await prisma.userSavedList.update(data) - const flattenedResult = { - ...result, - organizations: result.organizations.map((x) => x.organizationId), - services: result.services.map((x) => x.serviceId), - } - return flattenedResult - } catch (error) { - handleError(error) + const result = await prisma.userSavedList.update(data) + const flattenedResult = { + ...result, + organizations: result.organizations.map((x) => x.organizationId), + services: result.services.map((x) => x.serviceId), } + return flattenedResult } diff --git a/packages/api/router/savedLists/mutation.saveItem.handler.ts b/packages/api/router/savedLists/mutation.saveItem.handler.ts index bb70efe5af..a509c78391 100644 --- a/packages/api/router/savedLists/mutation.saveItem.handler.ts +++ b/packages/api/router/savedLists/mutation.saveItem.handler.ts @@ -1,31 +1,26 @@ import { type z } from 'zod' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TSaveItemSchema, ZSaveItemSchema } from './mutation.saveItem.schema' export const saveItem = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { dataParser } = ZSaveItemSchema() + const { dataParser } = ZSaveItemSchema() - const inputData = { - actorId: ctx.session.user.id, - ownedById: ctx.session.user.id, - operation: 'LINK', - data: input, - } satisfies z.input - const data = dataParser.parse(inputData) + const inputData = { + actorId: ctx.session.user.id, + ownedById: ctx.session.user.id, + operation: 'LINK', + data: input, + } satisfies z.input + const data = dataParser.parse(inputData) - const result = await prisma.userSavedList.update(data) - const flattenedResult = { - ...result, - organizations: result.organizations.map((x) => x.organizationId), - services: result.services.map((x) => x.serviceId), - } - return flattenedResult - } catch (error) { - handleError(error) + const result = await prisma.userSavedList.update(data) + const flattenedResult = { + ...result, + organizations: result.organizations.map((x) => x.organizationId), + services: result.services.map((x) => x.serviceId), } + return flattenedResult } diff --git a/packages/api/router/savedLists/mutation.shareUrl.handler.ts b/packages/api/router/savedLists/mutation.shareUrl.handler.ts index 703cf25f63..bcd41fbb27 100644 --- a/packages/api/router/savedLists/mutation.shareUrl.handler.ts +++ b/packages/api/router/savedLists/mutation.shareUrl.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { nanoUrl } from '~api/lib/nanoIdUrl' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' @@ -19,25 +18,21 @@ const generateUniqueSlug = async (): Promise => { return slug } export const shareUrl = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const urlSlug = await generateUniqueSlug() - const from = { sharedLinkKey: null } + const urlSlug = await generateUniqueSlug() + const from = { sharedLinkKey: null } - const data = { sharedLinkKey: urlSlug } - const result = await prisma.userSavedList.update({ - where: input, - data: { - ...data, - auditLogs: CreateAuditLog({ actorId: ctx.session.user.id, operation: 'UPDATE', from, to: data }), - }, - select: { - id: true, - name: true, - sharedLinkKey: true, - }, - }) - return result - } catch (error) { - handleError(error) - } + const data = { sharedLinkKey: urlSlug } + const result = await prisma.userSavedList.update({ + where: input, + data: { + ...data, + auditLogs: CreateAuditLog({ actorId: ctx.session.user.id, operation: 'UPDATE', from, to: data }), + }, + select: { + id: true, + name: true, + sharedLinkKey: true, + }, + }) + return result } diff --git a/packages/api/router/savedLists/mutation.unShareUrl.handler.ts b/packages/api/router/savedLists/mutation.unShareUrl.handler.ts index 232754de84..b13b12f40f 100644 --- a/packages/api/router/savedLists/mutation.unShareUrl.handler.ts +++ b/packages/api/router/savedLists/mutation.unShareUrl.handler.ts @@ -1,40 +1,35 @@ import { TRPCError } from '@trpc/server' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TUnShareUrlSchema } from './mutation.unShareUrl.schema' export const unShareUrl = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const result = await prisma.$transaction(async (tx) => { - const from = await tx.userSavedList.findUniqueOrThrow({ - where: input, - select: { sharedLinkKey: true }, - }) + const result = await prisma.$transaction(async (tx) => { + const from = await tx.userSavedList.findUniqueOrThrow({ + where: input, + select: { sharedLinkKey: true }, + }) - if (from.sharedLinkKey === null) - throw new TRPCError({ code: 'BAD_REQUEST', message: `No shared URL for listId ${input.id}` }) + if (from.sharedLinkKey === null) + throw new TRPCError({ code: 'BAD_REQUEST', message: `No shared URL for listId ${input.id}` }) - const data = { sharedLinkKey: null } - const sharedUrl = await tx.userSavedList.update({ - where: input, - data: { - ...data, - auditLogs: CreateAuditLog({ actorId: ctx.session.user.id, operation: 'UPDATE', from, to: data }), - }, - select: { - id: true, - name: true, - sharedLinkKey: true, - }, - }) - return sharedUrl + const data = { sharedLinkKey: null } + const sharedUrl = await tx.userSavedList.update({ + where: input, + data: { + ...data, + auditLogs: CreateAuditLog({ actorId: ctx.session.user.id, operation: 'UPDATE', from, to: data }), + }, + select: { + id: true, + name: true, + sharedLinkKey: true, + }, }) - return result - } catch (error) { - handleError(error) - } + return sharedUrl + }) + return result } diff --git a/packages/api/router/savedLists/query.getAll.handler.ts b/packages/api/router/savedLists/query.getAll.handler.ts index 04aca33afe..c22ab5d413 100644 --- a/packages/api/router/savedLists/query.getAll.handler.ts +++ b/packages/api/router/savedLists/query.getAll.handler.ts @@ -1,27 +1,22 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' -export const getAll = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const lists = await prisma.userSavedList.findMany({ - where: { - ownedById: ctx.session.user.id, - }, - select: { - _count: { - select: { - organizations: true, - services: true, - sharedWith: true, - }, +export const getAll = async ({ ctx }: TRPCHandlerParams) => { + const lists = await prisma.userSavedList.findMany({ + where: { + ownedById: ctx.session.user.id, + }, + select: { + _count: { + select: { + organizations: true, + services: true, + sharedWith: true, }, - id: true, - name: true, }, - }) - return lists - } catch (error) { - handleError(error) - } + id: true, + name: true, + }, + }) + return lists } diff --git a/packages/api/router/savedLists/query.getById.handler.ts b/packages/api/router/savedLists/query.getById.handler.ts index 1767fe5cc2..b2c4537909 100644 --- a/packages/api/router/savedLists/query.getById.handler.ts +++ b/packages/api/router/savedLists/query.getById.handler.ts @@ -1,35 +1,30 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetByIdSchema } from './query.getById.schema' export const getById = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const list = await prisma.userSavedList.findFirst({ - where: { - id: input.id, - OR: [ - { - ownedById: ctx.session.user.id, - }, - { - sharedWith: { - some: { - userId: ctx.session.user.id, - }, + const list = await prisma.userSavedList.findFirst({ + where: { + id: input.id, + OR: [ + { + ownedById: ctx.session.user.id, + }, + { + sharedWith: { + some: { + userId: ctx.session.user.id, }, }, - ], - }, - include: { - organizations: true, - services: true, - sharedWith: true, - }, - }) - return list - } catch (error) { - handleError(error) - } + }, + ], + }, + include: { + organizations: true, + services: true, + sharedWith: true, + }, + }) + return list } diff --git a/packages/api/router/savedLists/query.getByUrl.handler.ts b/packages/api/router/savedLists/query.getByUrl.handler.ts index 372f0936dc..84076d36e3 100644 --- a/packages/api/router/savedLists/query.getByUrl.handler.ts +++ b/packages/api/router/savedLists/query.getByUrl.handler.ts @@ -1,23 +1,18 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetByUrlSchema } from './query.getByUrl.schema' export const getByUrl = async ({ input }: TRPCHandlerParams) => { - try { - const list = await prisma.userSavedList.findUniqueOrThrow({ - where: { - sharedLinkKey: input.slug, - }, - include: { - organizations: true, - services: true, - }, - }) + const list = await prisma.userSavedList.findUniqueOrThrow({ + where: { + sharedLinkKey: input.slug, + }, + include: { + organizations: true, + services: true, + }, + }) - return list - } catch (error) { - handleError(error) - } + return list } diff --git a/packages/api/router/savedLists/query.isSaved.handler.ts b/packages/api/router/savedLists/query.isSaved.handler.ts index 5f37e1cd2c..aa46765bf6 100644 --- a/packages/api/router/savedLists/query.isSaved.handler.ts +++ b/packages/api/router/savedLists/query.isSaved.handler.ts @@ -1,33 +1,28 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TIsSavedSchema } from './query.isSaved.schema' export const isSaved = async ({ ctx, input }: TRPCHandlerParams) => { - try { - if (!ctx.session?.user.id) return false + if (!ctx.session?.user.id) return false - const result = await prisma.userSavedList.findMany({ - where: { - AND: [ - { ownedById: ctx.session.user.id }, - { - OR: [ - { organizations: { some: { organizationId: input } } }, - { services: { some: { serviceId: input } } }, - ], - }, - ], - }, - select: { - id: true, - name: true, - }, - }) - if (!result.length) return false - return result - } catch (error) { - handleError(error) - } + const result = await prisma.userSavedList.findMany({ + where: { + AND: [ + { ownedById: ctx.session.user.id }, + { + OR: [ + { organizations: { some: { organizationId: input } } }, + { services: { some: { serviceId: input } } }, + ], + }, + ], + }, + select: { + id: true, + name: true, + }, + }) + if (!result.length) return false + return result } diff --git a/packages/api/router/service/mutation.attachServiceAttribute.handler.ts b/packages/api/router/service/mutation.attachServiceAttribute.handler.ts index 49de9c14b6..814d8ad136 100644 --- a/packages/api/router/service/mutation.attachServiceAttribute.handler.ts +++ b/packages/api/router/service/mutation.attachServiceAttribute.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { @@ -11,31 +10,27 @@ export const attachServiceAttribute = async ({ ctx, input, }: TRPCHandlerParams) => { - try { - const inputData = { - actorId: ctx.actorId, - operation: 'CREATE', - data: input, - } - const { attributeSupplement, auditLogs, freeText, serviceAttribute, translationKey } = - ZAttachServiceAttributeSchema().dataParser.parse(inputData) - - const result = await prisma.$transaction(async (tx) => { - const tKey = translationKey ? await tx.translationKey.create(translationKey) : undefined - const fText = freeText ? await tx.freeText.create(freeText) : undefined - const aSupp = attributeSupplement ? await tx.attributeSupplement.create(attributeSupplement) : undefined - const attrLink = await tx.serviceAttribute.create(serviceAttribute) - const logs = await tx.auditLog.createMany({ data: auditLogs, skipDuplicates: true }) - return { - translationKey: tKey, - freeText: fText, - attributeSupplement: aSupp, - serviceAttribute: attrLink, - auditLog: logs, - } - }) - return result - } catch (error) { - handleError(error) + const inputData = { + actorId: ctx.actorId, + operation: 'CREATE', + data: input, } + const { attributeSupplement, auditLogs, freeText, serviceAttribute, translationKey } = + ZAttachServiceAttributeSchema().dataParser.parse(inputData) + + const result = await prisma.$transaction(async (tx) => { + const tKey = translationKey ? await tx.translationKey.create(translationKey) : undefined + const fText = freeText ? await tx.freeText.create(freeText) : undefined + const aSupp = attributeSupplement ? await tx.attributeSupplement.create(attributeSupplement) : undefined + const attrLink = await tx.serviceAttribute.create(serviceAttribute) + const logs = await tx.auditLog.createMany({ data: auditLogs, skipDuplicates: true }) + return { + translationKey: tKey, + freeText: fText, + attributeSupplement: aSupp, + serviceAttribute: attrLink, + auditLog: logs, + } + }) + return result } diff --git a/packages/api/router/service/mutation.attachServiceTags.handler.ts b/packages/api/router/service/mutation.attachServiceTags.handler.ts index 8e1461fc70..b65872b096 100644 --- a/packages/api/router/service/mutation.attachServiceTags.handler.ts +++ b/packages/api/router/service/mutation.attachServiceTags.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TAttachServiceTagsSchema, ZAttachServiceTagsSchema } from './mutation.attachServiceTags.schema' @@ -8,20 +7,16 @@ export const attachServiceTags = async ({ ctx, input, }: TRPCHandlerParams) => { - try { - const inputData = { - actorId: ctx.actorId, - operation: 'LINK', - to: input, - } - const { auditLogs, orgServiceTag } = ZAttachServiceTagsSchema().dataParser.parse(inputData) - const results = await prisma.$transaction(async (tx) => { - const tags = await tx.orgServiceTag.createMany(orgServiceTag) - const logs = await tx.auditLog.createMany(auditLogs) - return { orgServiceTag: tags.count, auditLog: logs.count } - }) - return results - } catch (error) { - handleError(error) + const inputData = { + actorId: ctx.actorId, + operation: 'LINK', + to: input, } + const { auditLogs, orgServiceTag } = ZAttachServiceTagsSchema().dataParser.parse(inputData) + const results = await prisma.$transaction(async (tx) => { + const tags = await tx.orgServiceTag.createMany(orgServiceTag) + const logs = await tx.auditLog.createMany(auditLogs) + return { orgServiceTag: tags.count, auditLog: logs.count } + }) + return results } diff --git a/packages/api/router/service/mutation.create.handler.ts b/packages/api/router/service/mutation.create.handler.ts index e952a7340f..44383081a2 100644 --- a/packages/api/router/service/mutation.create.handler.ts +++ b/packages/api/router/service/mutation.create.handler.ts @@ -1,19 +1,12 @@ -import { TRPCError } from '@trpc/server' - import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateSchema, ZCreateSchema } from './mutation.create.schema' export const create = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const inputData = { actorId: ctx.session.user.id, operation: 'CREATE', data: input } + const inputData = { actorId: ctx.session.user.id, operation: 'CREATE', data: input } - const record = ZCreateSchema().dataParser.parse(inputData) - const result = await prisma.orgService.create(record) - return result - } catch (error) { - handleError(error) - } + const record = ZCreateSchema().dataParser.parse(inputData) + const result = await prisma.orgService.create(record) + return result } diff --git a/packages/api/router/service/mutation.createAccessInstructions.handler.ts b/packages/api/router/service/mutation.createAccessInstructions.handler.ts index 6077b23310..579a64fb5d 100644 --- a/packages/api/router/service/mutation.createAccessInstructions.handler.ts +++ b/packages/api/router/service/mutation.createAccessInstructions.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { @@ -11,27 +10,23 @@ export const createAccessInstructions = async ({ ctx, input, }: TRPCHandlerParams) => { - try { - const inputData = { actorId: ctx.actorId, operation: 'CREATE', data: input } + const inputData = { actorId: ctx.actorId, operation: 'CREATE', data: input } - const { serviceAccessAttribute, attributeSupplement, auditLogs, freeText, translationKey } = - ZCreateAccessInstructionsSchema().dataParser.parse(inputData) - const result = await prisma.$transaction(async (tx) => { - const tKey = translationKey ? await tx.translationKey.create(translationKey) : undefined - const fText = freeText ? await tx.freeText.create(freeText) : undefined - const aSupp = attributeSupplement ? await tx.attributeSupplement.create(attributeSupplement) : undefined - const attrLink = await tx.serviceAccessAttribute.create(serviceAccessAttribute) - const logs = await tx.auditLog.createMany({ data: auditLogs, skipDuplicates: true }) - return { - translationKey: tKey, - freeText: fText, - attributeSupplement: aSupp, - serviceAccessAttribute: attrLink, - auditLog: logs, - } - }) - return result - } catch (error) { - handleError(error) - } + const { serviceAccessAttribute, attributeSupplement, auditLogs, freeText, translationKey } = + ZCreateAccessInstructionsSchema().dataParser.parse(inputData) + const result = await prisma.$transaction(async (tx) => { + const tKey = translationKey ? await tx.translationKey.create(translationKey) : undefined + const fText = freeText ? await tx.freeText.create(freeText) : undefined + const aSupp = attributeSupplement ? await tx.attributeSupplement.create(attributeSupplement) : undefined + const attrLink = await tx.serviceAccessAttribute.create(serviceAccessAttribute) + const logs = await tx.auditLog.createMany({ data: auditLogs, skipDuplicates: true }) + return { + translationKey: tKey, + freeText: fText, + attributeSupplement: aSupp, + serviceAccessAttribute: attrLink, + auditLog: logs, + } + }) + return result } diff --git a/packages/api/router/service/mutation.createServiceArea.handler.ts b/packages/api/router/service/mutation.createServiceArea.handler.ts index dfafac6e1b..98ec913797 100644 --- a/packages/api/router/service/mutation.createServiceArea.handler.ts +++ b/packages/api/router/service/mutation.createServiceArea.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateServiceAreaSchema, ZCreateServiceAreaSchema } from './mutation.createServiceArea.schema' @@ -8,33 +7,29 @@ export const createServiceArea = async ({ ctx, input, }: TRPCHandlerParams) => { - try { - const inputData = { - actorId: ctx.actorId, - operation: 'CREATE', - data: input, - } - const { serviceArea, auditLog, serviceAreaCountry, serviceAreaDist } = - ZCreateServiceAreaSchema().dataParser.parse(inputData) - const result = await prisma.$transaction(async (tx) => { - const area = await tx.serviceArea.create(serviceArea) - const countries = serviceAreaCountry - ? await tx.serviceAreaCountry.createMany({ data: serviceAreaCountry, skipDuplicates: true }) - : undefined - const districts = serviceAreaDist - ? await tx.serviceAreaDist.createMany({ data: serviceAreaDist, skipDuplicates: true }) - : undefined - const logs = await tx.auditLog.createMany({ data: auditLog, skipDuplicates: true }) - - return { - serviceArea: area.id, - auditLog: logs.count, - serviceAreaCountry: countries?.count ?? 0, - serviceAreaDist: districts?.count ?? 0, - } - }) - return result - } catch (error) { - handleError(error) + const inputData = { + actorId: ctx.actorId, + operation: 'CREATE', + data: input, } + const { serviceArea, auditLog, serviceAreaCountry, serviceAreaDist } = + ZCreateServiceAreaSchema().dataParser.parse(inputData) + const result = await prisma.$transaction(async (tx) => { + const area = await tx.serviceArea.create(serviceArea) + const countries = serviceAreaCountry + ? await tx.serviceAreaCountry.createMany({ data: serviceAreaCountry, skipDuplicates: true }) + : undefined + const districts = serviceAreaDist + ? await tx.serviceAreaDist.createMany({ data: serviceAreaDist, skipDuplicates: true }) + : undefined + const logs = await tx.auditLog.createMany({ data: auditLog, skipDuplicates: true }) + + return { + serviceArea: area.id, + auditLog: logs.count, + serviceAreaCountry: countries?.count ?? 0, + serviceAreaDist: districts?.count ?? 0, + } + }) + return result } diff --git a/packages/api/router/service/mutation.linkEmails.handler.ts b/packages/api/router/service/mutation.linkEmails.handler.ts index 4163ad2681..8860a2a660 100644 --- a/packages/api/router/service/mutation.linkEmails.handler.ts +++ b/packages/api/router/service/mutation.linkEmails.handler.ts @@ -1,24 +1,19 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TLinkEmailsSchema, ZLinkEmailsSchema } from './mutation.linkEmails.schema' export const linkEmails = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const inputData = { - actorId: ctx.actorId, - operation: 'CREATE', - data: input, - } - const { auditLog, orgServiceEmail } = ZLinkEmailsSchema().dataParser.parse(inputData) - const result = await prisma.$transaction(async (tx) => { - const links = await tx.orgServiceEmail.createMany({ data: orgServiceEmail, skipDuplicates: true }) - const logs = await tx.auditLog.createMany({ data: auditLog, skipDuplicates: true }) - return { orgServiceEmail: links.count, auditLog: logs.count } - }) - return result - } catch (error) { - handleError(error) + const inputData = { + actorId: ctx.actorId, + operation: 'CREATE', + data: input, } + const { auditLog, orgServiceEmail } = ZLinkEmailsSchema().dataParser.parse(inputData) + const result = await prisma.$transaction(async (tx) => { + const links = await tx.orgServiceEmail.createMany({ data: orgServiceEmail, skipDuplicates: true }) + const logs = await tx.auditLog.createMany({ data: auditLog, skipDuplicates: true }) + return { orgServiceEmail: links.count, auditLog: logs.count } + }) + return result } diff --git a/packages/api/router/service/mutation.linkPhones.handler.ts b/packages/api/router/service/mutation.linkPhones.handler.ts index bfced378fd..dcc540a069 100644 --- a/packages/api/router/service/mutation.linkPhones.handler.ts +++ b/packages/api/router/service/mutation.linkPhones.handler.ts @@ -1,24 +1,19 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TLinkPhonesSchema, ZLinkPhonesSchema } from './mutation.linkPhones.schema' export const linkPhones = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const inputData = { - actorId: ctx.actorId, - operation: 'CREATE', - data: input, - } - const { auditLog, orgServicePhone } = ZLinkPhonesSchema().dataParser.parse(inputData) - const result = await prisma.$transaction(async (tx) => { - const links = await tx.orgServicePhone.createMany({ data: orgServicePhone, skipDuplicates: true }) - const logs = await tx.auditLog.createMany({ data: auditLog, skipDuplicates: true }) - return { orgServicePhone: links.count, auditLog: logs.count } - }) - return result - } catch (error) { - handleError(error) + const inputData = { + actorId: ctx.actorId, + operation: 'CREATE', + data: input, } + const { auditLog, orgServicePhone } = ZLinkPhonesSchema().dataParser.parse(inputData) + const result = await prisma.$transaction(async (tx) => { + const links = await tx.orgServicePhone.createMany({ data: orgServicePhone, skipDuplicates: true }) + const logs = await tx.auditLog.createMany({ data: auditLog, skipDuplicates: true }) + return { orgServicePhone: links.count, auditLog: logs.count } + }) + return result } diff --git a/packages/api/router/service/mutation.update.handler.ts b/packages/api/router/service/mutation.update.handler.ts index 1468ab54d0..ec210fccd8 100644 --- a/packages/api/router/service/mutation.update.handler.ts +++ b/packages/api/router/service/mutation.update.handler.ts @@ -1,26 +1,21 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { CreateAuditLog } from '~api/schemas/create/auditLog' import { type TRPCHandlerParams } from '~api/types/handler' import { type TUpdateSchema } from './mutation.update.schema' export const update = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { where, data } = input - const updatedRecord = await prisma.$transaction(async (tx) => { - const current = await tx.orgService.findUniqueOrThrow({ where }) - const updated = await tx.orgService.update({ - where, - data: { - ...data, - auditLogs: CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from: current, to: data }), - }, - }) - return updated + const { where, data } = input + const updatedRecord = await prisma.$transaction(async (tx) => { + const current = await tx.orgService.findUniqueOrThrow({ where }) + const updated = await tx.orgService.update({ + where, + data: { + ...data, + auditLogs: CreateAuditLog({ actorId: ctx.actorId, operation: 'UPDATE', from: current, to: data }), + }, }) - return updatedRecord - } catch (error) { - handleError(error) - } + return updated + }) + return updatedRecord } diff --git a/packages/api/router/service/query.byId.handler.ts b/packages/api/router/service/query.byId.handler.ts index c416aac48d..f1da95c2ff 100644 --- a/packages/api/router/service/query.byId.handler.ts +++ b/packages/api/router/service/query.byId.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalSelect, globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' @@ -7,147 +6,143 @@ import { type TByIdSchema } from './query.byId.schema' import { select } from './selects' export const byId = async ({ input }: TRPCHandlerParams) => { - try { - const result = await prisma.orgService.findUniqueOrThrow({ - where: input, - select: { - serviceName: globalSelect.freeText(), - services: { - where: { - tag: { - active: true, - }, + const result = await prisma.orgService.findUniqueOrThrow({ + where: input, + select: { + serviceName: globalSelect.freeText(), + services: { + where: { + tag: { + active: true, }, - select: { - tag: { - select: { - defaultAttributes: { - select: { - attribute: { - select: { - tsKey: true, - tsNs: true, - }, + }, + select: { + tag: { + select: { + defaultAttributes: { + select: { + attribute: { + select: { + tsKey: true, + tsNs: true, }, }, }, - category: { - select: { - tsKey: true, - tsNs: true, - }, + }, + category: { + select: { + tsKey: true, + tsNs: true, }, - tsKey: true, - tsNs: true, - active: true, }, + tsKey: true, + tsNs: true, + active: true, }, }, }, - serviceAreas: { - select: { - countries: { - select: { - country: globalSelect.country(), - }, + }, + serviceAreas: { + select: { + countries: { + select: { + country: globalSelect.country(), }, - districts: { - select: { - govDist: globalSelect.govDistExpanded(), - }, + }, + districts: { + select: { + govDist: globalSelect.govDistExpanded(), }, }, }, - hours: { - select: { - dayIndex: true, - start: true, - end: true, - closed: true, - tz: true, - }, + }, + hours: { + select: { + dayIndex: true, + start: true, + end: true, + closed: true, + tz: true, }, - reviews: { - where: { - visible: true, - deleted: false, - }, - select: { - language: globalSelect.language(), - lcrCountry: globalSelect.country(), - lcrGovDist: globalSelect.govDistExpanded(), - translatedText: { - select: { - text: true, - language: { - select: { - localeCode: true, - }, + }, + reviews: { + where: { + visible: true, + deleted: false, + }, + select: { + language: globalSelect.language(), + lcrCountry: globalSelect.country(), + lcrGovDist: globalSelect.govDistExpanded(), + translatedText: { + select: { + text: true, + language: { + select: { + localeCode: true, }, }, }, }, }, - attributes: { - where: { attribute: { active: true, categories: { some: { category: { active: true } } } } }, - select: select.attributes(), - }, - phones: { - where: { active: true, phone: globalWhere.isPublic() }, - select: { phone: globalSelect.orgPhone() }, + }, + attributes: { + where: { attribute: { active: true, categories: { some: { category: { active: true } } } } }, + select: select.attributes(), + }, + phones: { + where: { active: true, phone: globalWhere.isPublic() }, + select: { phone: globalSelect.orgPhone() }, + }, + emails: { + where: { + active: true, + email: globalWhere.isPublic(), }, - emails: { - where: { - active: true, - email: globalWhere.isPublic(), - }, - select: { - email: { - include: { - title: { - select: { - tsKey: true, - tsNs: true, - }, + select: { + email: { + include: { + title: { + select: { + tsKey: true, + tsNs: true, }, }, }, }, }, - accessDetails: { - where: { - active: true, - }, - select: select.attributes(), + }, + accessDetails: { + where: { + active: true, }, - locations: { - where: { - active: true, - location: globalWhere.isPublic(), - }, - select: { - location: { - select: { - name: true, - street1: true, - street2: true, - city: true, - postCode: true, - primary: true, - govDist: globalSelect.govDistBasic(), - country: globalSelect.country(), - longitude: true, - latitude: true, - }, + select: select.attributes(), + }, + locations: { + where: { + active: true, + location: globalWhere.isPublic(), + }, + select: { + location: { + select: { + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + primary: true, + govDist: globalSelect.govDistBasic(), + country: globalSelect.country(), + longitude: true, + latitude: true, }, }, }, - id: true, - description: globalSelect.freeText(), }, - }) - return result - } catch (error) { - handleError(error) - } + id: true, + description: globalSelect.freeText(), + }, + }) + return result } diff --git a/packages/api/router/service/query.byOrgId.handler.ts b/packages/api/router/service/query.byOrgId.handler.ts index b111ca0bb7..c5360a8ba0 100644 --- a/packages/api/router/service/query.byOrgId.handler.ts +++ b/packages/api/router/service/query.byOrgId.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalSelect, globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' @@ -7,45 +6,41 @@ import { type TByOrgIdSchema } from './query.byOrgId.schema' import { select } from './selects' export const byOrgId = async ({ input }: TRPCHandlerParams) => { - try { - const results = await prisma.orgService.findMany({ - where: input, - select: { - id: true, - attributes: { - where: { active: true, attribute: globalWhere.attributesByTag(['offers-remote-services']) }, - select: select.attributes(), - }, - serviceName: globalSelect.freeText(), + const results = await prisma.orgService.findMany({ + where: input, + select: { + id: true, + attributes: { + where: { active: true, attribute: globalWhere.attributesByTag(['offers-remote-services']) }, + select: select.attributes(), + }, + serviceName: globalSelect.freeText(), - services: { - where: { tag: { active: true } }, - select: { - tag: { - select: { - category: { select: { tsKey: true, tsNs: true } }, - tsKey: true, - tsNs: true, - active: true, - }, + services: { + where: { tag: { active: true } }, + select: { + tag: { + select: { + category: { select: { tsKey: true, tsNs: true } }, + tsKey: true, + tsNs: true, + active: true, }, }, }, + }, - locations: { - where: { location: globalWhere.isPublic() }, - select: { - location: { - select: { - name: true, - }, + locations: { + where: { location: globalWhere.isPublic() }, + select: { + location: { + select: { + name: true, }, }, }, }, - }) - return results - } catch (error) { - handleError(error) - } + }, + }) + return results } diff --git a/packages/api/router/service/query.byOrgLocationId.handler.ts b/packages/api/router/service/query.byOrgLocationId.handler.ts index f666dbfe01..77693a15a8 100644 --- a/packages/api/router/service/query.byOrgLocationId.handler.ts +++ b/packages/api/router/service/query.byOrgLocationId.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalSelect, globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' @@ -7,147 +6,143 @@ import { type TByOrgLocationIdSchema } from './query.byOrgLocationId.schema' import { select } from './selects' export const byOrgLocationId = async ({ input }: TRPCHandlerParams) => { - try { - const results = await prisma.orgService.findMany({ - where: { locations: { some: input } }, - select: { - serviceName: globalSelect.freeText(), - services: { - where: { - tag: { - active: true, - }, + const results = await prisma.orgService.findMany({ + where: { locations: { some: input } }, + select: { + serviceName: globalSelect.freeText(), + services: { + where: { + tag: { + active: true, }, - select: { - tag: { - select: { - defaultAttributes: { - select: { - attribute: { - select: { - tsKey: true, - tsNs: true, - }, + }, + select: { + tag: { + select: { + defaultAttributes: { + select: { + attribute: { + select: { + tsKey: true, + tsNs: true, }, }, }, - category: { - select: { - tsKey: true, - tsNs: true, - }, + }, + category: { + select: { + tsKey: true, + tsNs: true, }, - tsKey: true, - tsNs: true, - active: true, }, + tsKey: true, + tsNs: true, + active: true, }, }, }, - serviceAreas: { - select: { - countries: { - select: { - country: globalSelect.country(), - }, + }, + serviceAreas: { + select: { + countries: { + select: { + country: globalSelect.country(), }, - districts: { - select: { - govDist: globalSelect.govDistExpanded(), - }, + }, + districts: { + select: { + govDist: globalSelect.govDistExpanded(), }, }, }, - hours: { - select: { - dayIndex: true, - start: true, - end: true, - closed: true, - tz: true, - }, + }, + hours: { + select: { + dayIndex: true, + start: true, + end: true, + closed: true, + tz: true, }, - reviews: { - where: { - visible: true, - deleted: false, - }, - select: { - language: globalSelect.language(), - lcrCountry: globalSelect.country(), - lcrGovDist: globalSelect.govDistExpanded(), - translatedText: { - select: { - text: true, - language: { - select: { - localeCode: true, - }, + }, + reviews: { + where: { + visible: true, + deleted: false, + }, + select: { + language: globalSelect.language(), + lcrCountry: globalSelect.country(), + lcrGovDist: globalSelect.govDistExpanded(), + translatedText: { + select: { + text: true, + language: { + select: { + localeCode: true, }, }, }, }, }, - attributes: { - where: { attribute: { active: true, categories: { some: { category: { active: true } } } } }, - select: select.attributes(), - }, - phones: { - where: { active: true, phone: globalWhere.isPublic() }, - select: { phone: globalSelect.orgPhone() }, + }, + attributes: { + where: { attribute: { active: true, categories: { some: { category: { active: true } } } } }, + select: select.attributes(), + }, + phones: { + where: { active: true, phone: globalWhere.isPublic() }, + select: { phone: globalSelect.orgPhone() }, + }, + emails: { + where: { + active: true, + email: globalWhere.isPublic(), }, - emails: { - where: { - active: true, - email: globalWhere.isPublic(), - }, - select: { - email: { - include: { - title: { - select: { - tsKey: true, - tsNs: true, - }, + select: { + email: { + include: { + title: { + select: { + tsKey: true, + tsNs: true, }, }, }, }, }, - accessDetails: { - where: { - active: true, - }, - select: select.attributes(), + }, + accessDetails: { + where: { + active: true, }, - locations: { - where: { - active: true, - location: globalWhere.isPublic(), - }, - select: { - location: { - select: { - name: true, - street1: true, - street2: true, - city: true, - postCode: true, - primary: true, - govDist: globalSelect.govDistBasic(), - country: globalSelect.country(), - longitude: true, - latitude: true, - }, + select: select.attributes(), + }, + locations: { + where: { + active: true, + location: globalWhere.isPublic(), + }, + select: { + location: { + select: { + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + primary: true, + govDist: globalSelect.govDistBasic(), + country: globalSelect.country(), + longitude: true, + latitude: true, }, }, }, - id: true, - description: globalSelect.freeText(), }, - }) - return results - } catch (error) { - handleError(error) - } + id: true, + description: globalSelect.freeText(), + }, + }) + return results } diff --git a/packages/api/router/service/query.byUserListId.handler.ts b/packages/api/router/service/query.byUserListId.handler.ts index d6622166e8..09b45d4efd 100644 --- a/packages/api/router/service/query.byUserListId.handler.ts +++ b/packages/api/router/service/query.byUserListId.handler.ts @@ -1,7 +1,4 @@ -import { TRPCError } from '@trpc/server' - import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalSelect, globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' @@ -9,148 +6,144 @@ import { type TByUserListIdSchema } from './query.byUserListId.schema' import { select } from './selects' export const byUserListId = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const results = await prisma.orgService.findMany({ - where: { userLists: { some: { list: { AND: { id: input.listId, ownedById: ctx.session.user.id } } } } }, - select: { - serviceName: globalSelect.freeText(), - services: { - where: { - tag: { - active: true, - }, + const results = await prisma.orgService.findMany({ + where: { userLists: { some: { list: { AND: { id: input.listId, ownedById: ctx.session.user.id } } } } }, + select: { + serviceName: globalSelect.freeText(), + services: { + where: { + tag: { + active: true, }, - select: { - tag: { - select: { - defaultAttributes: { - select: { - attribute: { - select: { - tsKey: true, - tsNs: true, - }, + }, + select: { + tag: { + select: { + defaultAttributes: { + select: { + attribute: { + select: { + tsKey: true, + tsNs: true, }, }, }, - category: { - select: { - tsKey: true, - tsNs: true, - }, + }, + category: { + select: { + tsKey: true, + tsNs: true, }, - tsKey: true, - tsNs: true, - active: true, }, + tsKey: true, + tsNs: true, + active: true, }, }, }, - serviceAreas: { - select: { - countries: { - select: { - country: globalSelect.country(), - }, + }, + serviceAreas: { + select: { + countries: { + select: { + country: globalSelect.country(), }, - districts: { - select: { - govDist: globalSelect.govDistExpanded(), - }, + }, + districts: { + select: { + govDist: globalSelect.govDistExpanded(), }, }, }, - hours: { - select: { - dayIndex: true, - start: true, - end: true, - closed: true, - tz: true, - }, + }, + hours: { + select: { + dayIndex: true, + start: true, + end: true, + closed: true, + tz: true, }, - reviews: { - where: { - visible: true, - deleted: false, - }, - select: { - language: globalSelect.language(), - lcrCountry: globalSelect.country(), - lcrGovDist: globalSelect.govDistExpanded(), - translatedText: { - select: { - text: true, - language: { - select: { - localeCode: true, - }, + }, + reviews: { + where: { + visible: true, + deleted: false, + }, + select: { + language: globalSelect.language(), + lcrCountry: globalSelect.country(), + lcrGovDist: globalSelect.govDistExpanded(), + translatedText: { + select: { + text: true, + language: { + select: { + localeCode: true, }, }, }, }, }, - attributes: { - where: { attribute: { active: true, categories: { some: { category: { active: true } } } } }, - select: select.attributes(), - }, - phones: { - where: { active: true, phone: globalWhere.isPublic() }, - select: { phone: globalSelect.orgPhone() }, + }, + attributes: { + where: { attribute: { active: true, categories: { some: { category: { active: true } } } } }, + select: select.attributes(), + }, + phones: { + where: { active: true, phone: globalWhere.isPublic() }, + select: { phone: globalSelect.orgPhone() }, + }, + emails: { + where: { + active: true, + email: globalWhere.isPublic(), }, - emails: { - where: { - active: true, - email: globalWhere.isPublic(), - }, - select: { - email: { - include: { - title: { - select: { - tsKey: true, - tsNs: true, - }, + select: { + email: { + include: { + title: { + select: { + tsKey: true, + tsNs: true, }, }, }, }, }, - accessDetails: { - where: { - active: true, - }, - select: select.attributes(), + }, + accessDetails: { + where: { + active: true, }, - locations: { - where: { - active: true, - location: globalWhere.isPublic(), - }, - select: { - location: { - select: { - name: true, - street1: true, - street2: true, - city: true, - postCode: true, - primary: true, - govDist: globalSelect.govDistBasic(), - country: globalSelect.country(), - longitude: true, - latitude: true, - }, + select: select.attributes(), + }, + locations: { + where: { + active: true, + location: globalWhere.isPublic(), + }, + select: { + location: { + select: { + name: true, + street1: true, + street2: true, + city: true, + postCode: true, + primary: true, + govDist: globalSelect.govDistBasic(), + country: globalSelect.country(), + longitude: true, + latitude: true, }, }, }, - id: true, - description: globalSelect.freeText(), }, - }) + id: true, + description: globalSelect.freeText(), + }, + }) - return results - } catch (error) { - handleError(error) - } + return results } diff --git a/packages/api/router/service/query.forServiceDrawer.handler.ts b/packages/api/router/service/query.forServiceDrawer.handler.ts index b205ab94a1..64ef092de2 100644 --- a/packages/api/router/service/query.forServiceDrawer.handler.ts +++ b/packages/api/router/service/query.forServiceDrawer.handler.ts @@ -1,7 +1,6 @@ import mapObjectVals from 'just-map-values' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { transformer } from '~api/lib/transformer' import { globalSelect, globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' @@ -10,76 +9,72 @@ import { type TForServiceDrawerSchema } from './query.forServiceDrawer.schema' import { select } from './selects' export const forServiceDrawer = async ({ input }: TRPCHandlerParams) => { - try { - const results = await prisma.orgService.findMany({ - where: input, - select: { - id: true, - attributes: { - where: { active: true, attribute: globalWhere.attributesByTag(['offers-remote-services']) }, - select: select.attributes(), - }, - serviceName: globalSelect.freeText(), + const results = await prisma.orgService.findMany({ + where: input, + select: { + id: true, + attributes: { + where: { active: true, attribute: globalWhere.attributesByTag(['offers-remote-services']) }, + select: select.attributes(), + }, + serviceName: globalSelect.freeText(), - services: { - where: { tag: { active: true } }, - select: { - tag: { - select: { - category: { select: { tsKey: true, tsNs: true, id: true } }, - tsKey: true, - tsNs: true, - active: true, - }, + services: { + where: { tag: { active: true } }, + select: { + tag: { + select: { + category: { select: { tsKey: true, tsNs: true, id: true } }, + tsKey: true, + tsNs: true, + active: true, }, }, }, + }, - locations: { - where: { location: globalWhere.isPublic() }, - select: { - location: { - select: { - name: true, - }, + locations: { + where: { location: globalWhere.isPublic() }, + select: { + location: { + select: { + name: true, }, }, }, }, - }) + }, + }) - let servObj: ServObj = {} - for (const service of results) { - servObj = service.services.reduce((items: ServObj, record) => { - const key = record.tag.category.tsKey - if (!items[key]) { - items[key] = new Set() - } - const itemToAdd = { - id: service.id, - name: { - tsNs: service.serviceName?.ns, - tsKey: service.serviceName?.key, - defaultText: service.serviceName?.tsKey.text, - }, - locations: service.locations.map(({ location }) => location.name), - attributes: service.attributes.map(({ attribute }) => { - const { id, tsKey, tsNs } = attribute - return { id, tsKey, tsNs } - }), - } satisfies ServItem + let servObj: ServObj = {} + for (const service of results) { + servObj = service.services.reduce((items: ServObj, record) => { + const key = record.tag.category.tsKey + if (!items[key]) { + items[key] = new Set() + } + const itemToAdd = { + id: service.id, + name: { + tsNs: service.serviceName?.ns, + tsKey: service.serviceName?.key, + defaultText: service.serviceName?.tsKey.text, + }, + locations: service.locations.map(({ location }) => location.name), + attributes: service.attributes.map(({ attribute }) => { + const { id, tsKey, tsNs } = attribute + return { id, tsKey, tsNs } + }), + } satisfies ServItem - items[key]?.add(transformer.stringify(itemToAdd)) - return items - }, servObj) - } - const transformed = mapObjectVals(servObj, (value) => - [...value].map((item) => transformer.parse(item)) - ) - return transformed - } catch (error) { - handleError(error) + items[key]?.add(transformer.stringify(itemToAdd)) + return items + }, servObj) } + const transformed = mapObjectVals(servObj, (value) => + [...value].map((item) => transformer.parse(item)) + ) + return transformed } type ServObj = { [k: string]: Set } diff --git a/packages/api/router/service/query.forServiceEditDrawer.handler.ts b/packages/api/router/service/query.forServiceEditDrawer.handler.ts index 3998af2d89..ad267346b5 100644 --- a/packages/api/router/service/query.forServiceEditDrawer.handler.ts +++ b/packages/api/router/service/query.forServiceEditDrawer.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { transformer } from '~api/lib/transformer' import { globalSelect } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' @@ -7,101 +6,97 @@ import { type TRPCHandlerParams } from '~api/types/handler' import { type TForServiceEditDrawerSchema } from './query.forServiceEditDrawer.schema' export const forServiceEditDrawer = async ({ input }: TRPCHandlerParams) => { - try { - const result = await prisma.orgService.findUniqueOrThrow({ - where: { id: input }, - select: { - id: true, - accessDetails: { - select: { - attribute: { select: { id: true, tsKey: true, tsNs: true } }, - supplement: { - select: { id: true, text: globalSelect.freeText({ withCrowdinId: true }), data: true }, - }, + const result = await prisma.orgService.findUniqueOrThrow({ + where: { id: input }, + select: { + id: true, + accessDetails: { + select: { + attribute: { select: { id: true, tsKey: true, tsNs: true } }, + supplement: { + select: { id: true, text: globalSelect.freeText({ withCrowdinId: true }), data: true }, }, }, - attributes: { - select: { - attribute: { - select: { - id: true, - tsKey: true, - tsNs: true, - icon: true, - categories: { select: { category: { select: { tag: true } } } }, - }, + }, + attributes: { + select: { + attribute: { + select: { + id: true, + tsKey: true, + tsNs: true, + icon: true, + categories: { select: { category: { select: { tag: true } } } }, }, - supplement: { - select: { - id: true, - active: true, - data: true, - boolean: true, - countryId: true, - govDistId: true, - languageId: true, - text: globalSelect.freeText({ withCrowdinId: true }), - }, + }, + supplement: { + select: { + id: true, + active: true, + data: true, + boolean: true, + countryId: true, + govDistId: true, + languageId: true, + text: globalSelect.freeText({ withCrowdinId: true }), }, }, }, - description: globalSelect.freeText({ withCrowdinId: true }), - phones: { select: { phone: { select: { id: true } } } }, - emails: { select: { email: { select: { id: true } } } }, - locations: { select: { location: { select: { id: true } } } }, - hours: { select: { id: true, dayIndex: true, start: true, end: true, closed: true, tz: true } }, - services: { select: { tag: { select: { id: true, categoryId: true } } } }, - serviceAreas: { - select: { - id: true, - countries: { select: { country: { select: { id: true } } } }, - districts: { select: { govDist: { select: { id: true } } } }, - }, + }, + description: globalSelect.freeText({ withCrowdinId: true }), + phones: { select: { phone: { select: { id: true } } } }, + emails: { select: { email: { select: { id: true } } } }, + locations: { select: { location: { select: { id: true } } } }, + hours: { select: { id: true, dayIndex: true, start: true, end: true, closed: true, tz: true } }, + services: { select: { tag: { select: { id: true, categoryId: true } } } }, + serviceAreas: { + select: { + id: true, + countries: { select: { country: { select: { id: true } } } }, + districts: { select: { govDist: { select: { id: true } } } }, }, - published: true, - deleted: true, - serviceName: globalSelect.freeText({ withCrowdinId: true }), }, - }) - const { attributes, phones, emails, locations, services, serviceAreas, accessDetails, ...rest } = result - const transformed = { - ...rest, - phones: phones.map(({ phone }) => phone.id), - emails: emails.map(({ email }) => email.id), - locations: locations.map(({ location }) => location.id), - services: services.map(({ tag }) => ({ id: tag.id, categoryId: tag.categoryId })), - serviceAreas: serviceAreas - ? { - id: serviceAreas.id, - countries: serviceAreas.countries.map(({ country }) => country.id), - districts: serviceAreas.districts.map(({ govDist }) => govDist.id), - } - : null, - attributes: attributes.map(({ attribute, supplement }) => { - const { categories, ...attr } = attribute - return { - attribute: { ...attr, categories: categories.map(({ category }) => category.tag) }, - supplement: supplement.map(({ data, ...rest }) => { - if (data) { - return { ...rest, data: transformer.parse(JSON.stringify(data)) } - } - return { ...rest, data } - }), - } - }), - accessDetails: accessDetails.map(({ attribute, supplement }) => ({ - attribute, + published: true, + deleted: true, + serviceName: globalSelect.freeText({ withCrowdinId: true }), + }, + }) + const { attributes, phones, emails, locations, services, serviceAreas, accessDetails, ...rest } = result + const transformed = { + ...rest, + phones: phones.map(({ phone }) => phone.id), + emails: emails.map(({ email }) => email.id), + locations: locations.map(({ location }) => location.id), + services: services.map(({ tag }) => ({ id: tag.id, categoryId: tag.categoryId })), + serviceAreas: serviceAreas + ? { + id: serviceAreas.id, + countries: serviceAreas.countries.map(({ country }) => country.id), + districts: serviceAreas.districts.map(({ govDist }) => govDist.id), + } + : null, + attributes: attributes.map(({ attribute, supplement }) => { + const { categories, ...attr } = attribute + return { + attribute: { ...attr, categories: categories.map(({ category }) => category.tag) }, supplement: supplement.map(({ data, ...rest }) => { if (data) { return { ...rest, data: transformer.parse(JSON.stringify(data)) } } return { ...rest, data } }), - })), - } - - return transformed - } catch (error) { - handleError(error) + } + }), + accessDetails: accessDetails.map(({ attribute, supplement }) => ({ + attribute, + supplement: supplement.map(({ data, ...rest }) => { + if (data) { + return { ...rest, data: transformer.parse(JSON.stringify(data)) } + } + return { ...rest, data } + }), + })), } + + return transformed } diff --git a/packages/api/router/service/query.forServiceInfoCard.handler.ts b/packages/api/router/service/query.forServiceInfoCard.handler.ts index 055f6c4adf..ed729cec58 100644 --- a/packages/api/router/service/query.forServiceInfoCard.handler.ts +++ b/packages/api/router/service/query.forServiceInfoCard.handler.ts @@ -1,51 +1,46 @@ import { isIdFor, prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' import { type TForServiceInfoCardSchema } from './query.forServiceInfoCard.schema' export const forServiceInfoCard = async ({ input }: TRPCHandlerParams) => { - try { - const result = await prisma.orgService.findMany({ - where: { - ...globalWhere.isPublic(), - ...(isIdFor('organization', input.parentId) - ? { organization: { id: input.parentId, ...globalWhere.isPublic() } } - : { locations: { some: { location: { id: input.parentId, ...globalWhere.isPublic() } } } }), - ...(input.remoteOnly - ? { attributes: { some: { attribute: { active: true, tag: 'offers-remote-services' } } } } - : {}), - OR: [{ crisisSupportOnly: null }, { crisisSupportOnly: false }], - }, - select: { - id: true, - serviceName: { select: { key: true, ns: true, tsKey: { select: { text: true } } } }, - services: { - select: { - tag: { - select: { tsKey: true, category: { select: { tsKey: true } } }, - }, + const result = await prisma.orgService.findMany({ + where: { + ...globalWhere.isPublic(), + ...(isIdFor('organization', input.parentId) + ? { organization: { id: input.parentId, ...globalWhere.isPublic() } } + : { locations: { some: { location: { id: input.parentId, ...globalWhere.isPublic() } } } }), + ...(input.remoteOnly + ? { attributes: { some: { attribute: { active: true, tag: 'offers-remote-services' } } } } + : {}), + OR: [{ crisisSupportOnly: null }, { crisisSupportOnly: false }], + }, + select: { + id: true, + serviceName: { select: { key: true, ns: true, tsKey: { select: { text: true } } } }, + services: { + select: { + tag: { + select: { tsKey: true, category: { select: { tsKey: true } } }, }, - where: { tag: { active: true, category: { active: true } } }, - }, - attributes: { - where: { attribute: { active: true, tag: 'offers-remote-services' } }, - select: { attributeId: true }, }, + where: { tag: { active: true, category: { active: true } } }, + }, + attributes: { + where: { attribute: { active: true, tag: 'offers-remote-services' } }, + select: { attributeId: true }, }, - }) + }, + }) - const transformed = result.map(({ id, serviceName, services, attributes }) => ({ - id, - serviceName: serviceName - ? { tsKey: serviceName.tsKey, tsNs: serviceName.ns, defaultText: serviceName.tsKey.text } - : null, - serviceCategories: [...new Set(services.map(({ tag }) => tag.category.tsKey))].sort(), - offersRemote: attributes.length > 0, - })) - return transformed - } catch (error) { - handleError(error) - } + const transformed = result.map(({ id, serviceName, services, attributes }) => ({ + id, + serviceName: serviceName + ? { tsKey: serviceName.tsKey, tsNs: serviceName.ns, defaultText: serviceName.tsKey.text } + : null, + serviceCategories: [...new Set(services.map(({ tag }) => tag.category.tsKey))].sort(), + offersRemote: attributes.length > 0, + })) + return transformed } diff --git a/packages/api/router/service/query.forServiceModal.handler.ts b/packages/api/router/service/query.forServiceModal.handler.ts index 212c748502..208d4e6f72 100644 --- a/packages/api/router/service/query.forServiceModal.handler.ts +++ b/packages/api/router/service/query.forServiceModal.handler.ts @@ -1,5 +1,4 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' @@ -7,59 +6,55 @@ import { type TForServiceModalSchema } from './query.forServiceModal.schema' import { select } from './selects' export const forServiceModal = async ({ input }: TRPCHandlerParams) => { - try { - const result = await prisma.orgService.findUniqueOrThrow({ - where: { id: input, ...globalWhere.isPublic() }, - select: { - id: true, - services: { select: { tag: { select: { tsKey: true } } }, where: { tag: { active: true } } }, - accessDetails: { - where: { active: true }, + const result = await prisma.orgService.findUniqueOrThrow({ + where: { id: input, ...globalWhere.isPublic() }, + select: { + id: true, + services: { select: { tag: { select: { tsKey: true } } }, where: { tag: { active: true } } }, + accessDetails: { + where: { active: true }, - select: { - attribute: { select: { id: true } }, - supplement: { - where: { active: true }, - select: { - id: true, - data: true, - text: { select: { key: true, tsKey: { select: { text: true } } } }, - }, + select: { + attribute: { select: { id: true } }, + supplement: { + where: { active: true }, + select: { + id: true, + data: true, + text: { select: { key: true, tsKey: { select: { text: true } } } }, }, }, }, - serviceName: { - select: { - key: true, - ns: true, - tsKey: { - select: { - text: true, - }, + }, + serviceName: { + select: { + key: true, + ns: true, + tsKey: { + select: { + text: true, }, }, }, - locations: { - where: { location: globalWhere.isPublic() }, - select: { location: { select: { country: { select: { cca2: true } } } } }, - }, - attributes: { select: select.attributes() }, - hours: { where: { active: true }, select: { _count: true } }, - description: { - select: { - key: true, - ns: true, - tsKey: { - select: { - text: true, - }, + }, + locations: { + where: { location: globalWhere.isPublic() }, + select: { location: { select: { country: { select: { cca2: true } } } } }, + }, + attributes: { select: select.attributes() }, + hours: { where: { active: true }, select: { _count: true } }, + description: { + select: { + key: true, + ns: true, + tsKey: { + select: { + text: true, }, }, }, }, - }) - return result - } catch (error) { - handleError(error) - } + }, + }) + return result } diff --git a/packages/api/router/service/query.getFilterOptions.handler.ts b/packages/api/router/service/query.getFilterOptions.handler.ts index 3dd8e72ab9..5659823578 100644 --- a/packages/api/router/service/query.getFilterOptions.handler.ts +++ b/packages/api/router/service/query.getFilterOptions.handler.ts @@ -1,37 +1,32 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' export const getFilterOptions = async () => { - try { - const result = await prisma.serviceCategory.findMany({ - where: { - active: true, - OR: [{ crisisSupportOnly: null }, { crisisSupportOnly: false }], - }, - select: { - id: true, - tsKey: true, - tsNs: true, - services: { - where: { - active: true, - }, - select: { - id: true, - tsKey: true, - tsNs: true, - }, - orderBy: { - name: 'asc', - }, + const result = await prisma.serviceCategory.findMany({ + where: { + active: true, + OR: [{ crisisSupportOnly: null }, { crisisSupportOnly: false }], + }, + select: { + id: true, + tsKey: true, + tsNs: true, + services: { + where: { + active: true, + }, + select: { + id: true, + tsKey: true, + tsNs: true, + }, + orderBy: { + name: 'asc', }, }, - orderBy: { - category: 'asc', - }, - }) - return result - } catch (error) { - handleError(error) - } + }, + orderBy: { + category: 'asc', + }, + }) + return result } diff --git a/packages/api/router/service/query.getNames.handler.ts b/packages/api/router/service/query.getNames.handler.ts index 5c35ee4719..6f0b25a6f3 100644 --- a/packages/api/router/service/query.getNames.handler.ts +++ b/packages/api/router/service/query.getNames.handler.ts @@ -2,41 +2,36 @@ import { TRPCError } from '@trpc/server' import flush from 'just-flush' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetNamesSchema } from './query.getNames.schema' export const getNames = async ({ input }: TRPCHandlerParams) => { - try { - const { orgLocationId, organizationId } = input + const { orgLocationId, organizationId } = input - if (!orgLocationId && !organizationId) throw new TRPCError({ code: 'BAD_REQUEST' }) + if (!orgLocationId && !organizationId) throw new TRPCError({ code: 'BAD_REQUEST' }) - const results = await prisma.orgService.findMany({ - where: { - organizationId: organizationId, - ...(orgLocationId - ? { - locations: { - some: { orgLocationId }, - }, - } - : {}), - }, - select: { - id: true, - serviceName: { select: { key: true, tsKey: { select: { text: true } } } }, - }, + const results = await prisma.orgService.findMany({ + where: { + organizationId: organizationId, + ...(orgLocationId + ? { + locations: { + some: { orgLocationId }, + }, + } + : {}), + }, + select: { + id: true, + serviceName: { select: { key: true, tsKey: { select: { text: true } } } }, + }, + }) + const transformedResults = flush( + results.map(({ id, serviceName }) => { + if (!serviceName) return + return { id, tsKey: serviceName.key, defaultText: serviceName.tsKey.text } }) - const transformedResults = flush( - results.map(({ id, serviceName }) => { - if (!serviceName) return - return { id, tsKey: serviceName.key, defaultText: serviceName.tsKey.text } - }) - ) - return transformedResults - } catch (error) { - handleError(error) - } + ) + return transformedResults } diff --git a/packages/api/router/service/query.getNames.schema.ts b/packages/api/router/service/query.getNames.schema.ts index 93bdc56afd..61b0754afa 100644 --- a/packages/api/router/service/query.getNames.schema.ts +++ b/packages/api/router/service/query.getNames.schema.ts @@ -3,7 +3,7 @@ import { z } from 'zod' import { prefixedId } from '~api/schemas/idPrefix' export const ZGetNamesSchema = z.union([ - z.object({ organizationId: prefixedId('organization'), orgLocationId: z.never() }), - z.object({ organizationId: z.never(), orgLocationId: prefixedId('orgLocation') }), + z.object({ organizationId: prefixedId('organization'), orgLocationId: z.undefined().optional() }), + z.object({ organizationId: z.undefined().optional(), orgLocationId: prefixedId('orgLocation') }), ]) export type TGetNamesSchema = z.infer diff --git a/packages/api/router/service/query.getOptions.handler.ts b/packages/api/router/service/query.getOptions.handler.ts index da9a01288e..12d7720a7f 100644 --- a/packages/api/router/service/query.getOptions.handler.ts +++ b/packages/api/router/service/query.getOptions.handler.ts @@ -1,26 +1,21 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' export const getOptions = async () => { - try { - const result = await prisma.serviceTag.findMany({ - select: { - id: true, - active: true, - tsKey: true, - tsNs: true, - category: { - select: { - id: true, - active: true, - tsKey: true, - tsNs: true, - }, + const result = await prisma.serviceTag.findMany({ + select: { + id: true, + active: true, + tsKey: true, + tsNs: true, + category: { + select: { + id: true, + active: true, + tsKey: true, + tsNs: true, }, }, - }) - return result - } catch (error) { - handleError(error) - } + }, + }) + return result } diff --git a/packages/api/router/service/query.getParentName.handler.ts b/packages/api/router/service/query.getParentName.handler.ts index 35c4095c11..47de908ef1 100644 --- a/packages/api/router/service/query.getParentName.handler.ts +++ b/packages/api/router/service/query.getParentName.handler.ts @@ -1,33 +1,28 @@ import { TRPCError } from '@trpc/server' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TGetParentNameSchema } from './query.getParentName.schema' export const getParentName = async ({ input }: TRPCHandlerParams) => { - try { - const { slug, orgLocationId } = input + const { slug, orgLocationId } = input - switch (true) { - case Boolean(slug): { - return prisma.organization.findUniqueOrThrow({ - where: { slug }, - select: { name: true }, - }) - } - case Boolean(orgLocationId): { - return prisma.orgLocation.findUniqueOrThrow({ - where: { id: orgLocationId }, - select: { name: true }, - }) - } - default: { - throw new TRPCError({ code: 'BAD_REQUEST' }) - } + switch (true) { + case Boolean(slug): { + return prisma.organization.findUniqueOrThrow({ + where: { slug }, + select: { name: true }, + }) + } + case Boolean(orgLocationId): { + return prisma.orgLocation.findUniqueOrThrow({ + where: { id: orgLocationId }, + select: { name: true }, + }) + } + default: { + throw new TRPCError({ code: 'BAD_REQUEST' }) } - } catch (error) { - handleError(error) } } diff --git a/packages/api/router/service/query.getParentName.schema.ts b/packages/api/router/service/query.getParentName.schema.ts index 2214a19326..681b058c3a 100644 --- a/packages/api/router/service/query.getParentName.schema.ts +++ b/packages/api/router/service/query.getParentName.schema.ts @@ -3,6 +3,6 @@ import { z } from 'zod' import { prefixedId } from '~api/schemas/idPrefix' export const ZGetParentNameSchema = z - .object({ slug: z.string(), orgLocationId: z.never() }) - .or(z.object({ orgLocationId: prefixedId('orgLocation'), slug: z.never() })) + .object({ slug: z.string(), orgLocationId: z.undefined() }) + .or(z.object({ orgLocationId: prefixedId('orgLocation'), slug: z.undefined() })) export type TGetParentNameSchema = z.infer diff --git a/packages/api/router/system/index.ts b/packages/api/router/system/index.ts index a338c24f85..2e570eb4c5 100644 --- a/packages/api/router/system/index.ts +++ b/packages/api/router/system/index.ts @@ -1,8 +1,5 @@ /* eslint-disable @typescript-eslint/consistent-type-imports */ -import superjson from 'superjson' -import { getEnv } from '@weareinreach/env' -import { handleError } from '~api/lib/errorHandler' import { defineRouter, permissionedProcedure, publicProcedure } from '~api/lib/trpc' import { permissionSubRouter } from './permission' @@ -11,62 +8,19 @@ import * as schema from './schemas' type SystemHandlerCache = { getFeatureFlag: typeof import('./query.getFeatureFlag.handler').getFeatureFlag + updateInactiveCountryEdgeConfig: typeof import('./mutation.updateInactiveCountryEdgeConfig.handler').updateInactiveCountryEdgeConfig } const HandlerCache: Partial = {} export const systemRouter = defineRouter({ permissions: permissionSubRouter, - updateInactiveCountryEdgeConfig: permissionedProcedure('attachOrgAttributes').mutation(async ({ ctx }) => { - try { - const active = await ctx.prisma.country.findMany({ - where: { activeForOrgs: true }, - select: { cca2: true }, - orderBy: { cca2: 'asc' }, - }) - const inactive = await ctx.prisma.country.findMany({ - where: { activeForOrgs: null }, - select: { cca2: true, tsKey: true }, - orderBy: { cca2: 'asc' }, - }) - const prefixed = await ctx.prisma.country.findMany({ - where: { articlePrefix: true }, - select: { cca2: true }, - orderBy: { cca2: 'asc' }, - }) - const updateEdgeConfig = await fetch( - 'https://api.vercel.com/v1/edge-config/ecfg_1sqfggbdhoelhs9pm6mlhv951pfu/items?teamId=team_7S6ZQ4FrzxNgd9QvlU8P73wc', - { - method: 'PATCH', - headers: { - Authorization: `Bearer ${getEnv('EDGE_CONFIG_TOKEN')}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - items: [ - { - operation: 'upsert', - key: 'inactiveCountries', - value: superjson.serialize(new Map(inactive.map(({ cca2, tsKey }) => [cca2, tsKey]))), - }, - { - operation: 'upsert', - key: 'activeCountries', - value: active.map(({ cca2 }) => cca2), - }, - { - operation: 'upsert', - key: 'prefixedCountries', - value: prefixed.map(({ cca2 }) => cca2), - }, - ], - }), - } - ) - const result = await updateEdgeConfig.json() - return result - } catch (error) { - handleError(error) - } + updateInactiveCountryEdgeConfig: permissionedProcedure('attachOrgAttributes').mutation(async () => { + if (!HandlerCache.updateInactiveCountryEdgeConfig) + HandlerCache.updateInactiveCountryEdgeConfig = await import( + './mutation.updateInactiveCountryEdgeConfig.handler' + ).then((mod) => mod.updateInactiveCountryEdgeConfig) + if (!HandlerCache.updateInactiveCountryEdgeConfig) throw new Error('Failed to load handler') + return HandlerCache.updateInactiveCountryEdgeConfig() }), getFeatureFlag: publicProcedure.input(schema.ZGetFeatureFlagSchema).query(async ({ input, ctx }) => { if (!HandlerCache.getFeatureFlag) diff --git a/packages/api/router/system/mutation.updateInactiveCountryEdgeConfig.handler.ts b/packages/api/router/system/mutation.updateInactiveCountryEdgeConfig.handler.ts new file mode 100644 index 0000000000..f62603586b --- /dev/null +++ b/packages/api/router/system/mutation.updateInactiveCountryEdgeConfig.handler.ts @@ -0,0 +1,53 @@ +import superjson from 'superjson' + +import { prisma } from '@weareinreach/db' +import { getEnv } from '@weareinreach/env' + +export const updateInactiveCountryEdgeConfig = async () => { + const active = await prisma.country.findMany({ + where: { activeForOrgs: true }, + select: { cca2: true }, + orderBy: { cca2: 'asc' }, + }) + const inactive = await prisma.country.findMany({ + where: { activeForOrgs: null }, + select: { cca2: true, tsKey: true }, + orderBy: { cca2: 'asc' }, + }) + const prefixed = await prisma.country.findMany({ + where: { articlePrefix: true }, + select: { cca2: true }, + orderBy: { cca2: 'asc' }, + }) + const updateEdgeConfig = await fetch( + 'https://api.vercel.com/v1/edge-config/ecfg_1sqfggbdhoelhs9pm6mlhv951pfu/items?teamId=team_7S6ZQ4FrzxNgd9QvlU8P73wc', + { + method: 'PATCH', + headers: { + Authorization: `Bearer ${getEnv('EDGE_CONFIG_TOKEN')}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + items: [ + { + operation: 'upsert', + key: 'inactiveCountries', + value: superjson.serialize(new Map(inactive.map(({ cca2, tsKey }) => [cca2, tsKey]))), + }, + { + operation: 'upsert', + key: 'activeCountries', + value: active.map(({ cca2 }) => cca2), + }, + { + operation: 'upsert', + key: 'prefixedCountries', + value: prefixed.map(({ cca2 }) => cca2), + }, + ], + }), + } + ) + const result = await updateEdgeConfig.json() + return result +} diff --git a/packages/api/router/system/permission.ts b/packages/api/router/system/permission.ts index 3da060b7e9..f09e117317 100644 --- a/packages/api/router/system/permission.ts +++ b/packages/api/router/system/permission.ts @@ -5,24 +5,22 @@ import { defineRouter, permissionedProcedure } from '~api/lib/trpc' import { CreateNew } from '~api/schemas/system/permissions' export const permissionSubRouter = defineRouter({ - new: permissionedProcedure('createPermission') - .input(CreateNew().inputSchema) - .mutation(async ({ ctx, input }) => { - try { - const { actorId } = ctx - const { dataParser } = CreateNew() - - const inputData = { - actorId, - data: input, - operation: 'CREATE', - } satisfies z.input - const data = dataParser.parse(inputData) - - const result = await ctx.prisma.permission.create(data) - return result - } catch (error) { - handleError(error) - } - }), + // new: permissionedProcedure('createPermission') + // .input(CreateNew().inputSchema) + // .mutation(async ({ ctx, input }) => { + // try { + // const { actorId } = ctx + // const { dataParser } = CreateNew() + // const inputData = { + // actorId, + // data: input, + // operation: 'CREATE', + // } satisfies z.input + // const data = dataParser.parse(inputData) + // const result = await ctx.prisma.permission.create(data) + // return result + // } catch (error) { + // handleError(error) + // } + // }), }) diff --git a/packages/api/router/user/mutation.adminCreate.handler.ts b/packages/api/router/user/mutation.adminCreate.handler.ts index 4650fb6a8e..a3361514ba 100644 --- a/packages/api/router/user/mutation.adminCreate.handler.ts +++ b/packages/api/router/user/mutation.adminCreate.handler.ts @@ -2,30 +2,25 @@ import { type z } from 'zod' import { createCognitoUser } from '@weareinreach/auth/lib/createUser' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TAdminCreateSchema, ZAdminCreateSchema } from './mutation.adminCreate.schema' export const adminCreate = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const inputData = { - actorId: ctx.session.user.id, - operation: 'CREATE', - data: input, - } satisfies z.input['dataParser']> + const inputData = { + actorId: ctx.session.user.id, + operation: 'CREATE', + data: input, + } satisfies z.input['dataParser']> - const recordData = ZAdminCreateSchema().dataParser.parse(inputData) - const newUser = await prisma.$transaction(async (tx) => { - const user = await tx.user.create(recordData.prisma) - const cognitoUser = await createCognitoUser(recordData.cognito) - return { - user, - cognitoUser, - } - }) - return newUser - } catch (error) { - handleError(error) - } + const recordData = ZAdminCreateSchema().dataParser.parse(inputData) + const newUser = await prisma.$transaction(async (tx) => { + const user = await tx.user.create(recordData.prisma) + const cognitoUser = await createCognitoUser(recordData.cognito) + return { + user, + cognitoUser, + } + }) + return newUser } diff --git a/packages/api/router/user/mutation.confirmAccount.handler.ts b/packages/api/router/user/mutation.confirmAccount.handler.ts index 05aa1c3e84..8e3d215533 100644 --- a/packages/api/router/user/mutation.confirmAccount.handler.ts +++ b/packages/api/router/user/mutation.confirmAccount.handler.ts @@ -1,16 +1,20 @@ import { confirmAccount as cognitoConfirmAccount } from '@weareinreach/auth/lib/confirmAccount' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TConfirmAccountSchema } from './mutation.confirmAccount.schema' export const confirmAccount = async ({ input }: TRPCHandlerParams) => { - try { - const { code, email } = input - const response = await cognitoConfirmAccount(email, code) - return response - } catch (error) { - handleError(error) - } + const { code, email } = input + const response = await cognitoConfirmAccount(email, code) + + await prisma.user.update({ + where: { + email, + }, + data: { + emailVerified: new Date(), + }, + }) + return response } diff --git a/packages/api/router/user/mutation.create.handler.ts b/packages/api/router/user/mutation.create.handler.ts index b8fdcc3027..459b4444bd 100644 --- a/packages/api/router/user/mutation.create.handler.ts +++ b/packages/api/router/user/mutation.create.handler.ts @@ -2,7 +2,6 @@ import { TRPCError } from '@trpc/server' import { createCognitoUser } from '@weareinreach/auth/lib/createUser' import { Prisma, prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TCreateSchema } from './mutation.create.schema' @@ -22,6 +21,6 @@ export const create = async ({ input }: TRPCHandlerParams) => { if (error instanceof Prisma.PrismaClientKnownRequestError) { if (error.code === 'P2002') throw new TRPCError({ code: 'CONFLICT', message: 'User already exists' }) } - handleError(error) + throw error } } diff --git a/packages/api/router/user/mutation.deleteAccount.handler.ts b/packages/api/router/user/mutation.deleteAccount.handler.ts index c9ad0050c1..52f8d5c77d 100644 --- a/packages/api/router/user/mutation.deleteAccount.handler.ts +++ b/packages/api/router/user/mutation.deleteAccount.handler.ts @@ -2,30 +2,28 @@ import { TRPCError } from '@trpc/server' import { deleteAccount as cognitoDeleteAccount } from '@weareinreach/auth/lib/deleteAccount' import { userLogin } from '@weareinreach/auth/lib/userLogin' +import { userSignOut } from '@weareinreach/auth/lib/userLogout' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TDeleteAccountSchema } from './mutation.deleteAccount.schema' export const deleteAccount = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { email } = ctx.session.user - const cognitoSession = await userLogin(email, input) - if (cognitoSession.success) { - const successful = await prisma.$transaction(async (tx) => { - await tx.user.update({ - where: { email }, - data: { active: false }, - }) - - await cognitoDeleteAccount(email) - return true + const { email } = ctx.session.user + const cognitoSession = await userLogin(email, input) + if (cognitoSession.success) { + const successful = await prisma.$transaction(async (tx) => { + await tx.user.update({ + where: { email }, + data: { active: false }, }) - if (successful) return true + await userSignOut(email) + await cognitoDeleteAccount(email) + return true + }) + if (successful) { + return true } - throw new TRPCError({ code: 'UNAUTHORIZED', message: 'incorrect credentials' }) - } catch (error) { - handleError(error) } + throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Incorrect credentials' }) } diff --git a/packages/api/router/user/mutation.forgotPassword.handler.ts b/packages/api/router/user/mutation.forgotPassword.handler.ts index 53641dc22b..8785a0375f 100644 --- a/packages/api/router/user/mutation.forgotPassword.handler.ts +++ b/packages/api/router/user/mutation.forgotPassword.handler.ts @@ -1,14 +1,9 @@ import { forgotPassword as cognitoForgotPassword } from '@weareinreach/auth/lib/forgotPassword' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TForgotPasswordSchema } from './mutation.forgotPassword.schema' export const forgotPassword = async ({ input }: TRPCHandlerParams) => { - try { - const response = await cognitoForgotPassword(input) - return response - } catch (error) { - handleError(error) - } + const response = await cognitoForgotPassword(input) + return response } diff --git a/packages/api/router/user/mutation.resetPassword.handler.ts b/packages/api/router/user/mutation.resetPassword.handler.ts index c170f57dc3..7c8b51fad9 100644 --- a/packages/api/router/user/mutation.resetPassword.handler.ts +++ b/packages/api/router/user/mutation.resetPassword.handler.ts @@ -1,15 +1,10 @@ import { resetPassword as cognitoResetPassword } from '@weareinreach/auth/lib/resetPassword' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TResetPasswordSchema } from './mutation.resetPassword.schema' -export const resetPassword = async ({ ctx, input }: TRPCHandlerParams) => { - try { - const { code, password, email } = input - const response = await cognitoResetPassword({ code, email, password }) - return response - } catch (error) { - handleError(error) - } +export const resetPassword = async ({ input }: TRPCHandlerParams) => { + const { code, password, email } = input + const response = await cognitoResetPassword({ code, email, password }) + return response } diff --git a/packages/api/router/user/mutation.submitSurvey.handler.ts b/packages/api/router/user/mutation.submitSurvey.handler.ts index 75f677032a..977296bbec 100644 --- a/packages/api/router/user/mutation.submitSurvey.handler.ts +++ b/packages/api/router/user/mutation.submitSurvey.handler.ts @@ -1,14 +1,9 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' import { type TSubmitSurveySchema } from './mutation.submitSurvey.schema' export const submitSurvey = async ({ input }: TRPCHandlerParams) => { - try { - const survey = await prisma.userSurvey.create(input) - return survey.id - } catch (error) { - handleError(error) - } + const survey = await prisma.userSurvey.create(input) + return survey.id } diff --git a/packages/api/router/user/query.getLocationPermissions.handler.ts b/packages/api/router/user/query.getLocationPermissions.handler.ts index a0b9df0a93..927b88ad10 100644 --- a/packages/api/router/user/query.getLocationPermissions.handler.ts +++ b/packages/api/router/user/query.getLocationPermissions.handler.ts @@ -1,20 +1,15 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' export const getLocationPermissions = async ({ ctx }: TRPCHandlerParams) => { - try { - const permissions = await prisma.locationPermission.findMany({ - where: { - userId: ctx.session.user.id, - }, - include: { - location: true, - permission: true, - }, - }) - return permissions - } catch (error) { - handleError(error) - } + const permissions = await prisma.locationPermission.findMany({ + where: { + userId: ctx.session.user.id, + }, + include: { + location: true, + permission: true, + }, + }) + return permissions } diff --git a/packages/api/router/user/query.getOrgPermissions.handler.ts b/packages/api/router/user/query.getOrgPermissions.handler.ts index d585dbf913..db176dcf22 100644 --- a/packages/api/router/user/query.getOrgPermissions.handler.ts +++ b/packages/api/router/user/query.getOrgPermissions.handler.ts @@ -1,20 +1,15 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' export const getOrgPermissions = async ({ ctx }: TRPCHandlerParams) => { - try { - const permissions = await prisma.organizationPermission.findMany({ - where: { - userId: ctx.session.user.id, - }, - include: { - organization: true, - permission: true, - }, - }) - return permissions - } catch (error) { - handleError(error) - } + const permissions = await prisma.organizationPermission.findMany({ + where: { + userId: ctx.session.user.id, + }, + include: { + organization: true, + permission: true, + }, + }) + return permissions } diff --git a/packages/api/router/user/query.getPermissions.handler.ts b/packages/api/router/user/query.getPermissions.handler.ts index 6150c45c14..ae9c4e5dc4 100644 --- a/packages/api/router/user/query.getPermissions.handler.ts +++ b/packages/api/router/user/query.getPermissions.handler.ts @@ -1,19 +1,14 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' export const getPermissions = async ({ ctx }: TRPCHandlerParams) => { - try { - const permissions = await prisma.userPermission.findMany({ - where: { - userId: ctx.session.user.id, - }, - include: { - permission: true, - }, - }) - return permissions - } catch (error) { - handleError(error) - } + const permissions = await prisma.userPermission.findMany({ + where: { + userId: ctx.session.user.id, + }, + include: { + permission: true, + }, + }) + return permissions } diff --git a/packages/api/router/user/query.getProfile.handler.ts b/packages/api/router/user/query.getProfile.handler.ts index 0ad0556c03..3d2d314c2f 100644 --- a/packages/api/router/user/query.getProfile.handler.ts +++ b/packages/api/router/user/query.getProfile.handler.ts @@ -1,25 +1,20 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { type TRPCHandlerParams } from '~api/types/handler' export const getProfile = async ({ ctx }: TRPCHandlerParams) => { - try { - const profile = await prisma.user.findUniqueOrThrow({ - where: { - id: ctx.session.user.id, - }, - select: { - id: true, - createdAt: true, - updatedAt: true, - name: true, - email: true, - image: true, - active: true, - }, - }) - return profile - } catch (error) { - handleError(error) - } + const profile = await prisma.user.findUniqueOrThrow({ + where: { + id: ctx.session.user.id, + }, + select: { + id: true, + createdAt: true, + updatedAt: true, + name: true, + email: true, + image: true, + active: true, + }, + }) + return profile } diff --git a/packages/api/router/user/query.surveyOptions.handler.ts b/packages/api/router/user/query.surveyOptions.handler.ts index 49a3c85f0f..605fc1a720 100644 --- a/packages/api/router/user/query.surveyOptions.handler.ts +++ b/packages/api/router/user/query.surveyOptions.handler.ts @@ -1,61 +1,55 @@ import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' -import { type TRPCHandlerParams } from '~api/types/handler' export const surveyOptions = async () => { - try { - const commonSelect = { id: true, tsKey: true, tsNs: true } + const commonSelect = { id: true, tsKey: true, tsNs: true } - const [immigration, sog, ethnicity, community, countries] = await Promise.all([ - prisma.userImmigration.findMany({ - select: { - ...commonSelect, - status: true, - }, - orderBy: { - status: 'asc', - }, - }), - prisma.userSOGIdentity.findMany({ - select: { - ...commonSelect, - identifyAs: true, - }, - orderBy: { - identifyAs: 'asc', - }, - }), - prisma.userEthnicity.findMany({ - select: { - ...commonSelect, - ethnicity: true, - }, - orderBy: { - ethnicity: 'asc', - }, - }), - prisma.userCommunity.findMany({ - select: { - ...commonSelect, - community: true, - }, - orderBy: { - community: 'asc', - }, - }), - prisma.country.findMany({ - select: { - ...commonSelect, - cca2: true, - }, - orderBy: { - name: 'asc', - }, - }), - ]) + const [immigration, sog, ethnicity, community, countries] = await Promise.all([ + prisma.userImmigration.findMany({ + select: { + ...commonSelect, + status: true, + }, + orderBy: { + status: 'asc', + }, + }), + prisma.userSOGIdentity.findMany({ + select: { + ...commonSelect, + identifyAs: true, + }, + orderBy: { + identifyAs: 'asc', + }, + }), + prisma.userEthnicity.findMany({ + select: { + ...commonSelect, + ethnicity: true, + }, + orderBy: { + ethnicity: 'asc', + }, + }), + prisma.userCommunity.findMany({ + select: { + ...commonSelect, + community: true, + }, + orderBy: { + community: 'asc', + }, + }), + prisma.country.findMany({ + select: { + ...commonSelect, + cca2: true, + }, + orderBy: { + name: 'asc', + }, + }), + ]) - return { community, countries, ethnicity, immigration, sog } - } catch (error) { - handleError(error) - } + return { community, countries, ethnicity, immigration, sog } } diff --git a/packages/api/schemas/common.ts b/packages/api/schemas/common.ts index adefc693a4..f6a6a3dd10 100644 --- a/packages/api/schemas/common.ts +++ b/packages/api/schemas/common.ts @@ -143,7 +143,7 @@ export const MutationBaseArray = ( .array(), }) -export const transformNullString = (val: string | null) => { +export const transformNullString = (val?: string | null) => { if (val === '' || val === 'NULL') return null return val } diff --git a/packages/ui/components/data-portal/AddressDrawer.tsx b/packages/ui/components/data-portal/AddressDrawer.tsx index 9cc317403f..fcc9ec8e4d 100644 --- a/packages/ui/components/data-portal/AddressDrawer.tsx +++ b/packages/ui/components/data-portal/AddressDrawer.tsx @@ -72,7 +72,7 @@ const FormSchema = z.object({ data: z .object({ name: z.string().nullable(), - street1: z.string(), + street1: z.string().nullish().transform(transformNullString), street2: z.string().nullable().transform(transformNullString), city: z.string(), postCode: z.string().nullable().transform(transformNullString), @@ -118,7 +118,7 @@ const _AddressDrawer = forwardRef(({ loca initialValues: { id: '', data: { accessible: {} } }, transformValues: FormSchema.transform(schemaTransform).parse, }) - const { id: organizationId, slug: orgSlug } = useOrgInfo() + const { id: organizationId } = useOrgInfo() const { t } = useTranslation(['attribute', 'country', 'gov-dist']) const { classes, cx } = useStyles() const variants = useCustomVariant() @@ -141,7 +141,7 @@ const _AddressDrawer = forwardRef(({ loca // #region Get org's services const { data: orgServices } = api.service.getNames.useQuery( - { organizationId }, + { organizationId: organizationId ?? '' }, { // !fix when issue resolved. select: (data) => data.map(({ id, defaultText }) => ({ value: id, label: defaultText })), @@ -281,7 +281,7 @@ const _AddressDrawer = forwardRef(({ loca return (
- {matchText(value, form.values.data?.street1)} + {matchText(value, form.values.data?.street1 ?? '')} {subheading} diff --git a/packages/ui/components/data-portal/EmailTableDrawer.tsx b/packages/ui/components/data-portal/EmailTableDrawer.tsx index 5f2f8218c2..5d5710fca0 100644 --- a/packages/ui/components/data-portal/EmailTableDrawer.tsx +++ b/packages/ui/components/data-portal/EmailTableDrawer.tsx @@ -29,7 +29,7 @@ import { getCoreRowModel, useReactTable, } from '@tanstack/react-table' -import { useTranslation } from 'next-i18next' +// import { useTranslation } from 'next-i18next' import { forwardRef } from 'react' import { z } from 'zod' @@ -44,7 +44,7 @@ import { PhoneEmailModal } from '~ui/modals/dataPortal/PhoneEmail' import { MultiSelectPopover } from './MultiSelectPopover' -const [FormProvider, useFormContext, useForm] = createFormContext<{ data: EmailTableColumns[] }>() +const [FormProvider, _useFormContext, useForm] = createFormContext<{ data: EmailTableColumns[] }>() const FormSchema = z.object({ orgSlug: z.string().optional(), @@ -120,14 +120,14 @@ export const _EmailTableDrawer = forwardRef { @@ -155,7 +155,7 @@ export const _EmailTableDrawer = forwardRef { + onChange={() => { const newValues = form.values.data.map(({ primary, ...rest }, i) => info.row.index === i ? { primary: true, ...rest } : { primary: false, ...rest } ) diff --git a/packages/ui/components/data-portal/PhoneTableDrawer.tsx b/packages/ui/components/data-portal/PhoneTableDrawer.tsx index 24390f9757..3db3f7eb01 100644 --- a/packages/ui/components/data-portal/PhoneTableDrawer.tsx +++ b/packages/ui/components/data-portal/PhoneTableDrawer.tsx @@ -43,7 +43,7 @@ import { PhoneEmailModal } from '~ui/modals/dataPortal/PhoneEmail' import { MultiSelectPopover } from './MultiSelectPopover' import { PhoneNumberEntry } from './PhoneNumberEntry' -const [FormProvider, useFormContext, useForm] = createFormContext<{ data: PhoneTableColumns[] }>() +const [FormProvider, _useFormContext, useForm] = createFormContext<{ data: PhoneTableColumns[] }>() const transformNullString = (val: string | null) => { if (val === '' || val === 'NULL') return null @@ -124,13 +124,13 @@ export const _PhoneTableDrawer = forwardRef { @@ -159,7 +159,7 @@ export const _PhoneTableDrawer = forwardRef { + onChange={() => { const newValues = form.values.data.map(({ primary, ...rest }, i) => info.row.index === i ? { primary: true, ...rest } : { primary: false, ...rest } ) diff --git a/packages/ui/components/data-portal/ServiceEditDrawer.tsx b/packages/ui/components/data-portal/ServiceEditDrawer.tsx index c09167e9a2..63bdac9380 100644 --- a/packages/ui/components/data-portal/ServiceEditDrawer.tsx +++ b/packages/ui/components/data-portal/ServiceEditDrawer.tsx @@ -1,19 +1,15 @@ import { Box, type ButtonProps, - Card, createPolymorphicComponent, createStyles, - Divider, Drawer, - Group, List, Modal, rem, Stack, Text, Title, - UnstyledButton, } from '@mantine/core' import { useForm } from '@mantine/form' import { useDisclosure } from '@mantine/hooks' @@ -21,11 +17,9 @@ import compact from 'just-compact' import { useTranslation } from 'next-i18next' import { forwardRef, type ReactNode, useEffect, useMemo } from 'react' -import { type ApiOutput } from '@weareinreach/api' import { BadgeGroup, type ServiceTagProps } from '~ui/components/core/Badge' import { Breadcrumb } from '~ui/components/core/Breadcrumb' import { useCustomVariant } from '~ui/hooks' -import { useOrgInfo } from '~ui/hooks/useOrgInfo' import { Icon } from '~ui/icon' import { trpc as api } from '~ui/lib/trpcClient' import { DataViewer } from '~ui/other/DataViewer' @@ -63,7 +57,6 @@ const _ServiceEditDrawer = forwardRef ({ serviceId, ...props }, ref) => { const [drawerOpened, drawerHandler] = useDisclosure(true) const [serviceModalOpened, serviceModalHandler] = useDisclosure(false) - const { id: organizationId, slug: orgSlug } = useOrgInfo() const { classes } = useStyles() const form = useForm() const variants = useCustomVariant() @@ -90,7 +83,7 @@ const _ServiceEditDrawer = forwardRef if (!form.values.services?.length || !allServices) return [] return compact( - form.values.services.map(({ id, categoryId }) => { + form.values.services.map(({ id }) => { const service = allServices.find((item) => item.id === id) if (service) { return { @@ -311,11 +304,12 @@ interface FormData { } | null attributes: Attribute[] accessDetails: { + id?: string attribute: { id: string; tsKey: string; tsNs: string } supplement: { id: string data: unknown - text: { id: string; key: string; ns: string; tsKey: { text: string; crowdinId: number | null } } | null + text: { id?: string; key: string; ns: string; tsKey: { text: string; crowdinId: number | null } } | null }[] }[] } diff --git a/packages/ui/components/sections/LocationCard.tsx b/packages/ui/components/sections/LocationCard.tsx index d67b131c31..9dc922e6e7 100644 --- a/packages/ui/components/sections/LocationCard.tsx +++ b/packages/ui/components/sections/LocationCard.tsx @@ -1,5 +1,6 @@ import { Card, Divider, Group, List, Stack, Title, useMantineTheme } from '@mantine/core' import { useElementSize } from '@mantine/hooks' +import compact from 'just-compact' import parsePhoneNumber, { type CountryCode } from 'libphonenumber-js' import { formatAddress } from 'localized-address-format' import { useRouter } from 'next/router' @@ -89,7 +90,7 @@ export const LocationCard = ({ remoteOnly, locationId }: LocationCardProps) => { : undefined const formattedAddress = formatAddress({ - addressLines: data.street2 ? [data.street1.trim(), data.street2.trim()] : [data.street1.trim()], + addressLines: compact([data.street1?.trim(), data.street2?.trim()]), locality: data.city.trim(), postalCode: data.postCode ? data.postCode.trim() : undefined, postalCountry: data.country, diff --git a/packages/ui/mockData/orgService.ts b/packages/ui/mockData/orgService.ts index 175d033192..58ccc2cedb 100644 --- a/packages/ui/mockData/orgService.ts +++ b/packages/ui/mockData/orgService.ts @@ -266,7 +266,6 @@ export const serviceData = { forServiceEditDrawer: { id: 'osvc_01GVH3VEVPF1KEKBTRVTV70WGV', description: { - id: 'ftxt_01GW2HT9EZ3Y7G4JY1X91ZPX5P', key: 'orgn_01GVH3V408N0YS7CDYAH3F2BMH.osvc_01GVH3VEVPF1KEKBTRVTV70WGV.description', ns: 'org-data', tsKey: { @@ -278,7 +277,6 @@ export const serviceData = { published: true, deleted: false, serviceName: { - id: 'ftxt_01GW2HT9F1PZAQCMGSWY07973D', key: 'orgn_01GVH3V408N0YS7CDYAH3F2BMH.osvc_01GVH3VEVPF1KEKBTRVTV70WGV.name', ns: 'org-data', tsKey: { @@ -400,7 +398,6 @@ export const serviceData = { { id: 'atts_01GW2HT9F0SPS3EBCQ710RCNTA', text: { - id: 'ftxt_01GW2HT9F0ACKFYVT7EHMKJPFN', key: 'orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F0SPS3EBCQ710RCNTA', ns: 'org-data', tsKey: { @@ -424,7 +421,6 @@ export const serviceData = { { id: 'atts_01GW2HT9F0638MD74PJ3SCWNXC', text: { - id: 'ftxt_01GW2HT9F0VGSCRNGE0Y06TJZQ', key: 'orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F0638MD74PJ3SCWNXC', ns: 'org-data', tsKey: { @@ -457,7 +453,6 @@ export const serviceData = { { id: 'atts_01GW2HT9F09GFRWM3JK2A43AWG', text: { - id: 'ftxt_01GW2HT9F0H3RBRWNQT73BKMKF', key: 'orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F09GFRWM3JK2A43AWG', ns: 'org-data', tsKey: { @@ -490,7 +485,6 @@ export const serviceData = { { id: 'atts_01GW2HT9F01W2M7FBSKSXAQ9R4', text: { - id: 'ftxt_01GW2HT9F0D57GDYZJWTP9258B', key: 'orgn_01GVH3V408N0YS7CDYAH3F2BMH.attribute.atts_01GW2HT9F01W2M7FBSKSXAQ9R4', ns: 'org-data', tsKey: { diff --git a/packages/ui/mockData/service.ts b/packages/ui/mockData/service.ts index 22e9527ff8..3bce9e0bae 100644 --- a/packages/ui/mockData/service.ts +++ b/packages/ui/mockData/service.ts @@ -1,4 +1,3 @@ -import { type ApiOutput } from '@weareinreach/api' import { getTRPCMock, type MockDataObject, type MockHandlerObject } from '~ui/lib/getTrpcMock' export const serviceData = { @@ -171,6 +170,7 @@ export const serviceData = { ns: 'org-data', tsKey: { text: 'Get education and employment services for youth ages 24 and under', + crowdinId: null, }, }, services: [ @@ -503,6 +503,7 @@ export const serviceData = { ns: 'org-data', tsKey: { text: 'The above are drop-in service hours for education. Drop-in hours for employment services are Monday, Tuesday: 10 a.m. to noon, and 2:30 to 4:30 p.m. Wednesday: 10 a.m. to noon, and 1 to 2 p.m. Thursday: 10 a.m. to noon, and 1 to 3 p.m. Friday: 10 a.m. to 1 p.m.', + crowdinId: null, }, }, govDist: null, @@ -554,6 +555,7 @@ export const serviceData = { ns: 'org-data', tsKey: { text: 'Call for more information.', + crowdinId: null, }, }, govDist: null, @@ -584,29 +586,13 @@ export const serviceData = { postCode: '94109', primary: true, govDist: { - id: 'gdst_01GW2HJ23GMD17FBJMJWD16PZ1', - name: 'California', - slug: 'us-california', - iso: 'US-CA', abbrev: 'CA', - country: { - cca2: 'US', - cca3: 'USA', - id: 'ctry_01GW2HHDK9M26M80SG63T21SVH', - name: 'United States', - dialCode: null, - flag: '🇺🇸', - tsKey: 'USA.name', - tsNs: 'country', - }, govDistType: { tsKey: 'type-state', tsNs: 'gov-dist', }, - isPrimary: true, tsKey: 'us-california', tsNs: 'gov-dist', - parent: null, }, country: { cca2: 'US', @@ -629,6 +615,7 @@ export const serviceData = { ns: 'org-data', tsKey: { text: 'Larkin Street Academy Services offers job readiness, college readiness, computer classes, job placement and retention, internships, tutoring, GED tutoring and classes, secondary and post-secondary school enrollment and support, mindfulness, visual and performing arts. Offices are open Monday through Thursday, 9:00 AM - 16:00 PM, appointments only.', + crowdinId: null, }, }, }, diff --git a/packages/ui/modals/dataPortal/PhoneEmail/fields.tsx b/packages/ui/modals/dataPortal/PhoneEmail/fields.tsx index aff2ac94f0..1445556d34 100644 --- a/packages/ui/modals/dataPortal/PhoneEmail/fields.tsx +++ b/packages/ui/modals/dataPortal/PhoneEmail/fields.tsx @@ -57,7 +57,7 @@ export const PhoneEmailFlags = ({ role }: PhoneEmailFlagsProps) => { const { id: organizationId } = useOrgInfo() const { data: serviceData, isLoading: isServiceLoading } = api.service.getNames.useQuery( - { organizationId }, + { organizationId: organizationId ?? '' }, { enabled: Boolean(organizationId), From 181ef90b15ebc6f6ffdbf6e06821aedf21888d1c Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Wed, 9 Aug 2023 13:54:51 -0400 Subject: [PATCH 42/63] add trailing new line to generated file --- packages/ui/icon/buildIcons.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/icon/buildIcons.ts b/packages/ui/icon/buildIcons.ts index 27e02ef13e..0987c53a7f 100644 --- a/packages/ui/icon/buildIcons.ts +++ b/packages/ui/icon/buildIcons.ts @@ -10,7 +10,7 @@ loadIcons() const generate = async () => { const iconList = `// generated file - do not modify directly\n// prettier-ignore\nexport const iconList = ${JSON.stringify( listIcons() - )} as const` + )} as const\n` writeFileSync('./icon/iconList.ts', iconList) From 8103f095ebc338eb446cf63d6f610dabb655963c Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Wed, 9 Aug 2023 14:06:08 -0400 Subject: [PATCH 43/63] fix lockfile --- pnpm-lock.yaml | 91 ++++++-------------------------------------------- 1 file changed, 10 insertions(+), 81 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19d4d1d81b..59550236db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1526,7 +1526,7 @@ importers: version: 7.2.2(@types/react-dom@18.2.7)(@types/react@18.2.19)(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-designs': specifier: 7.0.1 - version: 7.0.1(@storybook/addon-docs@7.2.1)(@storybook/addons@7.2.1)(@storybook/components@7.2.1)(@storybook/manager-api@7.2.1)(@storybook/preview-api@7.2.1)(@storybook/theming@7.2.1)(react-dom@18.2.0)(react@18.2.0) + version: 7.0.1(@storybook/addon-docs@7.2.2)(@storybook/addons@7.2.2)(@storybook/components@7.2.2)(@storybook/manager-api@7.2.2)(@storybook/preview-api@7.2.2)(@storybook/theming@7.2.2)(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-docs': specifier: 7.2.2 version: 7.2.2(@types/react-dom@18.2.7)(@types/react@18.2.19)(react-dom@18.2.0)(react@18.2.0) @@ -9290,7 +9290,8 @@ packages: - encoding - supports-color dev: true - /@storybook/addon-designs@7.0.1(@storybook/addon-docs@7.2.1)(@storybook/addons@7.2.1)(@storybook/components@7.2.1)(@storybook/manager-api@7.2.1)(@storybook/preview-api@7.2.1)(@storybook/theming@7.2.1)(react-dom@18.2.0)(react@18.2.0): + + /@storybook/addon-designs@7.0.1(@storybook/addon-docs@7.2.2)(@storybook/addons@7.2.2)(@storybook/components@7.2.2)(@storybook/manager-api@7.2.2)(@storybook/preview-api@7.2.2)(@storybook/theming@7.2.2)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-bp7eoOYnoKp48H7tx0nkhhi9y7qUBr2YE9pd2DdxlS+4s2REglVeh1bNbBEYg9QUKPeKgUoWLMonLMTuCUyLoQ==} peerDependencies: '@storybook/addon-docs': ^7.0.0 @@ -9308,12 +9309,12 @@ packages: optional: true dependencies: '@figspec/react': 1.0.3(react@18.2.0) - '@storybook/addon-docs': 7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) - '@storybook/addons': 7.2.1(react-dom@18.2.0)(react@18.2.0) - '@storybook/components': 7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) - '@storybook/manager-api': 7.2.1(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.2.1 - '@storybook/theming': 7.2.1(react-dom@18.2.0)(react@18.2.0) + '@storybook/addon-docs': 7.2.2(@types/react-dom@18.2.7)(@types/react@18.2.19)(react-dom@18.2.0)(react@18.2.0) + '@storybook/addons': 7.2.2(react-dom@18.2.0)(react@18.2.0) + '@storybook/components': 7.2.2(@types/react-dom@18.2.7)(@types/react@18.2.19)(react-dom@18.2.0)(react@18.2.0) + '@storybook/manager-api': 7.2.2(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.2.2 + '@storybook/theming': 7.2.2(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: true @@ -9549,27 +9550,6 @@ packages: - '@types/react-dom' dev: true - /@storybook/addons@6.5.16(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-p3DqQi+8QRL5k7jXhXmJZLsE/GqHqyY6PcoA1oNTJr0try48uhTGUOYkgzmqtDaa/qPFO5LP+xCPzZXckGtquQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - dependencies: - '@storybook/api': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/channels': 6.5.16 - '@storybook/client-logger': 6.5.16 - '@storybook/core-events': 6.5.16 - '@storybook/csf': 0.0.2--canary.4566f4d.1 - '@storybook/router': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/theming': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@types/webpack-env': 1.18.0 - core-js: 3.30.0 - global: 4.4.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - regenerator-runtime: 0.13.11 - dev: true - /@storybook/addons@7.2.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-yWBBpBcRyPP1deAjzWV9OAXrPfeRd/vRpJw09dWHzuD3xtnd3jZ2h+t1r9a5yTSQbP5GO1YdS/WOK5Uf9hcsuw==} peerDependencies: @@ -9583,33 +9563,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/api@6.5.16(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-HOsuT8iomqeTMQJrRx5U8nsC7lJTwRr1DhdD0SzlqL4c80S/7uuCy4IZvOt4sYQjOzW5fOo/kamcoBXyLproTA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 - dependencies: - '@storybook/channels': 6.5.16 - '@storybook/client-logger': 6.5.16 - '@storybook/core-events': 6.5.16 - '@storybook/csf': 0.0.2--canary.4566f4d.1 - '@storybook/router': 6.5.16(react-dom@18.2.0)(react@18.2.0) - '@storybook/semver': 7.3.2 - '@storybook/theming': 6.5.16(react-dom@18.2.0)(react@18.2.0) - core-js: 3.30.0 - fast-deep-equal: 3.1.3 - global: 4.4.0 - lodash: 4.17.21 - memoizerific: 1.11.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - regenerator-runtime: 0.13.11 - store2: 2.14.2 - telejson: 6.0.8 - ts-dedent: 2.2.0 - util-deprecate: 1.0.2 - dev: true - /@storybook/blocks@7.2.2(@types/react-dom@18.2.7)(@types/react@18.2.19)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-VgO46E5zA8oo/Cn8kT9o3xiFtnqxlqsRRGp5t8A1YqgN2OvYTtg5/PLS16XH8Qui/m9EvOoT7DlOmcqlp3Z78w==} peerDependencies: @@ -9740,14 +9693,6 @@ packages: - webpack-cli dev: true - /@storybook/channels@6.5.16: - resolution: {integrity: sha512-VylzaWQZaMozEwZPJdyJoz+0jpDa8GRyaqu9TGG6QGv+KU5POoZaGLDkRE7TzWkyyP0KQLo80K99MssZCpgSeg==} - dependencies: - core-js: 3.30.0 - ts-dedent: 2.2.0 - util-deprecate: 1.0.2 - dev: true - /@storybook/channels@7.2.2: resolution: {integrity: sha512-FkH5QzKkq7smtPlaKTWalJ+sN13H4dWtxdZ+ePFAXaubsBqGqo3Dw3e/hrbjrMqFjTwiKnmj5K5bjhdJcvzi1A==} dependencies: @@ -9817,13 +9762,6 @@ packages: '@storybook/preview-api': 7.2.2 dev: true - /@storybook/client-logger@6.5.16: - resolution: {integrity: sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==} - dependencies: - core-js: 3.30.0 - global: 4.4.0 - dev: true - /@storybook/client-logger@7.2.2: resolution: {integrity: sha512-ULqPNTJsJdlWTQt5V/hEv4CUq7GgrLzLvcjhKB9IYbp4a0gjhinfq7jBFIcXRE8BSOQLui2PDGE3SzElzOp5/g==} dependencies: @@ -9911,12 +9849,6 @@ packages: - supports-color dev: true - /@storybook/core-events@6.5.16: - resolution: {integrity: sha512-qMZQwmvzpH5F2uwNUllTPg6eZXr2OaYZQRRN8VZJiuorZzDNdAFmiVWMWdkThwmyLEJuQKXxqCL8lMj/7PPM+g==} - dependencies: - core-js: 3.30.0 - dev: true - /@storybook/core-events@7.2.2: resolution: {integrity: sha512-0MUsOygFSyYRIWHrVAA7Y7zBoehdILgK2AbnV42qescmPD48YyovkSRiGq0BwSWvuvMRq+094dp7sh2tkfSGHg==} dev: true @@ -10349,8 +10281,6 @@ packages: - supports-color dev: true - - /@storybook/router@7.2.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-cnJg43dm3dVifKkRBUsQ4wXC4sJOm46JAS95yRPeGACoHpJTcbCWk1n5GLYA7ozO+KFQSNdxHxPIjNqvnzMFiA==} peerDependencies: @@ -10436,7 +10366,6 @@ packages: ts-dedent: 2.2.0 dev: true - /@storybook/theming@7.2.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-B4nxcxl4IyVvB1NXwRi4yopAS73zl052f2zJi3kVghJbZ3tgPwgvi3CVpOs2D4pgmxOrKCgiLnzLrGTH+13+0A==} peerDependencies: @@ -23674,7 +23603,7 @@ packages: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} dev: true - /storybook-addon-pseudo-states@2.1.0(@storybook/components@7.2.1)(@storybook/core-events@7.2.1)(@storybook/manager-api@7.2.1)(@storybook/preview-api@7.2.1)(@storybook/theming@7.2.1)(react-dom@18.2.0)(react@18.2.0): + /storybook-addon-pseudo-states@2.1.0(@storybook/components@7.2.2)(@storybook/core-events@7.2.2)(@storybook/manager-api@7.2.2)(@storybook/preview-api@7.2.2)(@storybook/theming@7.2.2)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-AwbCL1OiZ16aIeXSP/IOovkMwXy7NTZqmjkz+UM2guSGjvogHNA95NhuVyWoqieE+QWUpGO48+MrBGMeeJcHOQ==} peerDependencies: '@storybook/components': ^7.0.0 || 7 From 1c73e87187b1fea7ad1803d118800bf4988b1a9f Mon Sep 17 00:00:00 2001 From: InReach Bot <108850934+InReach-svc@users.noreply.github.com> Date: Wed, 9 Aug 2023 18:08:58 +0000 Subject: [PATCH 44/63] chore: lint & format Signed-off-by: InReach Bot <108850934+InReach-svc@users.noreply.github.com> --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 24e17e4fee..c03346c53d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -77,7 +77,7 @@ "@mantine/utils": "6.0.18", "@storybook/addon-a11y": "7.2.2", "@storybook/addon-actions": "7.2.2", - "@storybook/addon-designs": "7.0.1", + "@storybook/addon-designs": "7.0.1", "@storybook/addon-docs": "7.2.2", "@storybook/addon-essentials": "7.2.2", "@storybook/addon-interactions": "7.2.2", From d71543ed3016ec6f46979e04a35f97012cfd7335 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Thu, 10 Aug 2023 10:29:12 -0400 Subject: [PATCH 45/63] deactive unused route --- packages/api/router/system/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/router/system/index.ts b/packages/api/router/system/index.ts index 2e570eb4c5..d023398f22 100644 --- a/packages/api/router/system/index.ts +++ b/packages/api/router/system/index.ts @@ -13,7 +13,7 @@ type SystemHandlerCache = { const HandlerCache: Partial = {} export const systemRouter = defineRouter({ - permissions: permissionSubRouter, + // permissions: permissionSubRouter, updateInactiveCountryEdgeConfig: permissionedProcedure('attachOrgAttributes').mutation(async () => { if (!HandlerCache.updateInactiveCountryEdgeConfig) HandlerCache.updateInactiveCountryEdgeConfig = await import( From 66ff414bfc86a62250dbe3a4a05fe0f399762c59 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:12:01 -0400 Subject: [PATCH 46/63] switch google maps lib --- packages/ui/package.json | 2 +- pnpm-lock.yaml | 84 +++++++++++++++------------------------- 2 files changed, 33 insertions(+), 53 deletions(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index c03346c53d..e3ebc2dc94 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -21,7 +21,7 @@ "with-env": "dotenv -e ../../.env --" }, "dependencies": { - "@react-google-maps/api": "2.19.2", + "@googlemaps/react-wrapper": "1.1.35", "@terraformer/wkt": "2.2.0", "@textea/json-viewer": "3.1.1", "@turf/helpers": "6.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 59550236db..7cd66b49d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1361,9 +1361,9 @@ importers: packages/ui: dependencies: - '@react-google-maps/api': - specifier: 2.19.2 - version: 2.19.2(react-dom@18.2.0)(react@18.2.0) + '@googlemaps/react-wrapper': + specifier: 1.1.35 + version: 1.1.35(react@18.2.0) '@terraformer/wkt': specifier: 2.2.0 version: 2.2.0 @@ -5748,11 +5748,13 @@ packages: fast-deep-equal: 3.1.3 dev: false - /@googlemaps/markerclusterer@2.3.2: - resolution: {integrity: sha512-zb9OQP8XscZp2Npt1uQUYnGKu1miuq4DPP28JyDuFd6HV17HCEcjV9MtBi4muG/iVRXXvuHW9bRCnHbao9ITfw==} + /@googlemaps/react-wrapper@1.1.35(react@18.2.0): + resolution: {integrity: sha512-vK+BDQMHN0Oqr66cW3ZPWVK43BUmJJBu6P8T74tc6/fKpUJUlFEaZsupgIIRRRDW9ejB8uGagUmwOnA2gdcvbw==} + peerDependencies: + react: '>=16.8.0 || 18' dependencies: - fast-deep-equal: 3.1.3 - supercluster: 8.0.1 + '@googlemaps/js-api-loader': 1.16.2 + react: 18.2.0 dev: false /@googlemaps/url-signature@1.0.29: @@ -6356,7 +6358,7 @@ packages: dependencies: '@mantine/ssr': 6.0.18(@emotion/react@11.11.1)(@emotion/server@11.11.0)(react-dom@18.2.0)(react@18.2.0) '@mantine/styles': 6.0.18(@emotion/react@11.11.1)(react-dom@18.2.0)(react@18.2.0) - next: 13.4.13(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) + next: 13.4.13(@babel/core@7.22.10)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) transitivePeerDependencies: @@ -8432,30 +8434,6 @@ packages: '@babel/runtime': 7.22.6 dev: true - /@react-google-maps/api@2.19.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Vt57XWzCKfsUjKOmFUl2erVVfOePkPK5OigF/f+q7UuV/Nm9KDDy1PMFBx+wNahEqOd6a32BxfsykEhBnbU9wQ==} - peerDependencies: - react: ^16.8 || ^17 || ^18 || 18 - react-dom: ^16.8 || ^17 || ^18 || 18 - dependencies: - '@googlemaps/js-api-loader': 1.16.2 - '@googlemaps/markerclusterer': 2.3.2 - '@react-google-maps/infobox': 2.19.2 - '@react-google-maps/marker-clusterer': 2.19.2 - '@types/google.maps': 3.53.5 - invariant: 2.2.4 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@react-google-maps/infobox@2.19.2: - resolution: {integrity: sha512-6wvBqeJsQ/eFSvoxg+9VoncQvNoVCdmxzxRpLvmjPD+nNC6mHM0vJH1xSqaKijkMrfLJT0nfkTGpovrF896jwg==} - dev: false - - /@react-google-maps/marker-clusterer@2.19.2: - resolution: {integrity: sha512-x9ibmsP0ZVqzyCo1Pitbw+4b6iEXRw/r1TCy3vOUR3eKrzWLnHYZMR325BkZW2r8fnuWE/V3Fp4QZOP9qYORCw==} - dev: false - /@remirror/core-constants@2.0.0: resolution: {integrity: sha512-vpePPMecHJllBqCWXl6+FIcZqS+tRUM2kSCCKFeEo1H3XUEv3ocijBIPhnlSAa7g6maX+12ATTgxrOsLpWVr2g==} dependencies: @@ -9897,7 +9875,7 @@ packages: util: 0.12.5 util-deprecate: 1.0.2 watchpack: 2.4.0 - ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) + ws: 8.13.0 transitivePeerDependencies: - bufferutil - encoding @@ -11012,7 +10990,7 @@ packages: '@trpc/client': 10.37.1(@trpc/server@10.37.1) '@trpc/react-query': 10.37.1(@tanstack/react-query@4.32.6)(@trpc/client@10.37.1)(@trpc/server@10.37.1)(react-dom@18.2.0)(react@18.2.0) '@trpc/server': 10.37.1 - next: 13.4.13(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) + next: 13.4.13(@babel/core@7.22.10)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-ssr-prepass: 1.5.0(react@18.2.0) @@ -11302,10 +11280,6 @@ packages: resolution: {integrity: sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==} dev: true - /@types/google.maps@3.53.5: - resolution: {integrity: sha512-HoRq4Te8J6krH7hj+TfdYepqegoKZCj3kkaK5gf+ySFSHLvyqYkDvkrtbcVJXQ6QBphQ0h1TF7p4J6sOh4r/zg==} - dev: false - /@types/google.maps@3.53.6: resolution: {integrity: sha512-zDU8c7K0YR1Ob7Wn0qCSQvICIuxilZH9KFIDHK6JO/5QzqEMv8e4+9bmyoDEktA9vPNmwP++zzg65h9y53iZ6Q==} dev: true @@ -13102,6 +13076,7 @@ packages: requiresBuild: true dependencies: node-gyp-build: 4.6.0 + dev: false /bufrw@1.3.0: resolution: {integrity: sha512-jzQnSbdJqhIltU9O5KUiTtljP9ccw2u5ix59McQy4pV2xGhVLhRZIndY8GIrgh5HjXa6+QJ9AQhOd2QWQizJFQ==} @@ -18563,10 +18538,6 @@ packages: safe-buffer: 5.2.1 dev: true - /kdbush@4.0.2: - resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} - dev: false - /keyv@4.5.2: resolution: {integrity: sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==} dependencies: @@ -20112,7 +20083,7 @@ packages: '@panva/hkdf': 1.0.4 cookie: 0.5.0 jose: 4.13.2 - next: 13.4.13(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) + next: 13.4.13(@babel/core@7.22.10)(react-dom@18.2.0)(react@18.2.0) oauth: 0.9.15 openid-client: 5.4.0 preact: 10.13.2 @@ -20136,7 +20107,7 @@ packages: hoist-non-react-statics: 3.3.2 i18next: 23.4.4 i18next-fs-backend: 2.1.5 - next: 13.4.13(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) + next: 13.4.13(@babel/core@7.22.10)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-i18next: 13.0.3(i18next@23.4.4)(react-dom@18.2.0)(react@18.2.0) @@ -20200,7 +20171,6 @@ packages: transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - dev: true /next@13.4.13(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-A3YVbVDNeXLhWsZ8Nf6IkxmNlmTNz0yVg186NJ97tGZqPDdPzTrHotJ+A1cuJm2XfuWPrKOUZILl5iBQkIf8Jw==} @@ -20249,7 +20219,7 @@ packages: next: '*' dependencies: chokidar: 3.5.3 - next: 13.4.13(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) + next: 13.4.13(@babel/core@7.22.10)(react-dom@18.2.0)(react@18.2.0) dev: false /nice-try@1.0.5: @@ -20344,6 +20314,7 @@ packages: /node-gyp-build@4.6.0: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} hasBin: true + dev: false /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -23942,12 +23913,6 @@ packages: - supports-color dev: false - /supercluster@8.0.1: - resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} - dependencies: - kdbush: 4.0.2 - dev: false - /superjson@1.13.1: resolution: {integrity: sha512-AVH2eknm9DEd3qvxM4Sq+LTCkSXE2ssfh1t11MHMXyYXFQyQ1HLgVvV+guLTsaQnJU3gnaVo34TohHPulY/wLg==} engines: {node: '>=10'} @@ -25078,6 +25043,7 @@ packages: requiresBuild: true dependencies: node-gyp-build: 4.6.0 + dev: false /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -25614,6 +25580,19 @@ packages: optional: true dev: true + /ws@8.13.0: + resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /ws@8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3): resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} engines: {node: '>=10.0.0'} @@ -25628,6 +25607,7 @@ packages: dependencies: bufferutil: 4.0.7 utf-8-validate: 6.0.3 + dev: false /xdg-basedir@5.1.0: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} From 19ac45ab3ba1e6f9e04f97c743af75b87d4e9691 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Thu, 10 Aug 2023 14:22:42 -0400 Subject: [PATCH 47/63] temp logging for intl route --- apps/app/src/middleware.ts | 1 + apps/app/src/pages/search/intl/[country].tsx | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/app/src/middleware.ts b/apps/app/src/middleware.ts index b1d8c7e254..706b4fd800 100644 --- a/apps/app/src/middleware.ts +++ b/apps/app/src/middleware.ts @@ -22,6 +22,7 @@ export const middleware: NextMiddleware = async (req: NextRequest) => { if (searchedCountry === 'dist') { url.pathname = url.pathname.replace(/\/dist\//, '/US/') } else { + console.log('middleware -> searchedCountry', searchedCountry) url.searchParams.forEach((_v, k) => url.searchParams.delete(k)) url.pathname = `/search/intl/${searchedCountry}` } diff --git a/apps/app/src/pages/search/intl/[country].tsx b/apps/app/src/pages/search/intl/[country].tsx index c7c5707fe4..edfc9c5831 100644 --- a/apps/app/src/pages/search/intl/[country].tsx +++ b/apps/app/src/pages/search/intl/[country].tsx @@ -151,12 +151,12 @@ export const getStaticPaths: GetStaticPaths = async () => { fallback: 'blocking', } } -export const getStaticProps = async ({ - params, - locale, -}: GetStaticPropsContext>) => { +export const getStaticProps = async (ctx: GetStaticPropsContext>) => { + const { params, locale } = ctx const parsedQuery = QuerySchema.safeParse(params) + console.log('getStaticProps -> parsedQuery', parsedQuery) if (!parsedQuery.success) { + console.log('Redirecting to 404', ctx) return { notFound: true, } From 2312bc45c6b9170cc981669345271e1881569324 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Thu, 10 Aug 2023 15:56:24 -0400 Subject: [PATCH 48/63] Revert "temp logging for intl route" This reverts commit 19ac45ab3ba1e6f9e04f97c743af75b87d4e9691. --- apps/app/src/middleware.ts | 1 - apps/app/src/pages/search/intl/[country].tsx | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/app/src/middleware.ts b/apps/app/src/middleware.ts index 706b4fd800..b1d8c7e254 100644 --- a/apps/app/src/middleware.ts +++ b/apps/app/src/middleware.ts @@ -22,7 +22,6 @@ export const middleware: NextMiddleware = async (req: NextRequest) => { if (searchedCountry === 'dist') { url.pathname = url.pathname.replace(/\/dist\//, '/US/') } else { - console.log('middleware -> searchedCountry', searchedCountry) url.searchParams.forEach((_v, k) => url.searchParams.delete(k)) url.pathname = `/search/intl/${searchedCountry}` } diff --git a/apps/app/src/pages/search/intl/[country].tsx b/apps/app/src/pages/search/intl/[country].tsx index edfc9c5831..c7c5707fe4 100644 --- a/apps/app/src/pages/search/intl/[country].tsx +++ b/apps/app/src/pages/search/intl/[country].tsx @@ -151,12 +151,12 @@ export const getStaticPaths: GetStaticPaths = async () => { fallback: 'blocking', } } -export const getStaticProps = async (ctx: GetStaticPropsContext>) => { - const { params, locale } = ctx +export const getStaticProps = async ({ + params, + locale, +}: GetStaticPropsContext>) => { const parsedQuery = QuerySchema.safeParse(params) - console.log('getStaticProps -> parsedQuery', parsedQuery) if (!parsedQuery.success) { - console.log('Redirecting to 404', ctx) return { notFound: true, } From 1acfedec044dc0cd468864f9650a81e2bb49bc66 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Thu, 10 Aug 2023 19:43:58 -0400 Subject: [PATCH 49/63] fix: where params --- packages/api/router/location/query.getByOrgId.handler.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/api/router/location/query.getByOrgId.handler.ts b/packages/api/router/location/query.getByOrgId.handler.ts index 9f94ba429c..04ab089ce8 100644 --- a/packages/api/router/location/query.getByOrgId.handler.ts +++ b/packages/api/router/location/query.getByOrgId.handler.ts @@ -10,7 +10,9 @@ import { select } from './selects' export const getByOrgId = async ({ ctx, input }: TRPCHandlerParams) => { const locations = await prisma.orgLocation.findMany({ where: { - id: input.orgId, + organization: { + id: input.orgId, + }, ...globalWhere.isPublic(), }, select: { From 9d7c600c11ec91fb2b15fbd5bf21e024db16eefe Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Thu, 10 Aug 2023 23:04:29 -0400 Subject: [PATCH 50/63] add deps --- packages/api/package.json | 1 + packages/ui/package.json | 3 +++ pnpm-lock.yaml | 54 ++++++++++++++++++++++++++------------- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index 4f31e00810..0f9f628d3a 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -55,6 +55,7 @@ }, "devDependencies": { "@types/eslint": "8.44.2", + "@types/google.maps": "3.53.6", "@types/luxon": "3.3.1", "@types/node": "18.17.4", "@types/prettier": "2.7.3", diff --git a/packages/ui/package.json b/packages/ui/package.json index e3ebc2dc94..8033532aec 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -25,6 +25,7 @@ "@terraformer/wkt": "2.2.0", "@textea/json-viewer": "3.1.1", "@turf/helpers": "6.5.0", + "@tweenjs/tween.js": "21.0.0", "@weareinreach/env": "workspace:*", "@weareinreach/util": "workspace:*", "ajv": "8.12.0", @@ -121,6 +122,7 @@ "@weareinreach/config": "workspace:*", "@weareinreach/db": "workspace:*", "@weareinreach/eslint-config": "0.100.0", + "@welldone-software/why-did-you-render": "7.0.1", "babel-loader": "9.1.3", "chromatic": "6.21.0", "css-loader": "6.8.1", @@ -146,6 +148,7 @@ "react": "18.2.0", "react-docgen-typescript": "2.2.2", "react-dom": "18.2.0", + "react-hook-tracer": "1.4.0", "react-i18next": "13.0.3", "resolve-url-loader": "5.0.0", "slugify": "1.6.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7cd66b49d4..6e51e90c91 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -686,6 +686,9 @@ importers: '@types/eslint': specifier: 8.44.2 version: 8.44.2 + '@types/google.maps': + specifier: 3.53.6 + version: 3.53.6 '@types/luxon': specifier: 3.3.1 version: 3.3.1 @@ -1373,6 +1376,9 @@ importers: '@turf/helpers': specifier: 6.5.0 version: 6.5.0 + '@tweenjs/tween.js': + specifier: 21.0.0 + version: 21.0.0 '@weareinreach/env': specifier: workspace:* version: link:../env @@ -1656,6 +1662,9 @@ importers: '@weareinreach/eslint-config': specifier: 0.100.0 version: link:../eslint-config + '@welldone-software/why-did-you-render': + specifier: 7.0.1 + version: 7.0.1(react@18.2.0) babel-loader: specifier: 9.1.3 version: 9.1.3(@babel/core@7.22.10)(webpack@5.88.2) @@ -1731,6 +1740,9 @@ importers: react-dom: specifier: 18.2.0 version: 18.2.0(react@18.2.0) + react-hook-tracer: + specifier: 1.4.0 + version: 1.4.0(react-dom@18.2.0)(react@18.2.0) react-i18next: specifier: 13.0.3 version: 13.0.3(i18next@23.4.4)(react-dom@18.2.0)(react@18.2.0) @@ -9875,7 +9887,7 @@ packages: util: 0.12.5 util-deprecate: 1.0.2 watchpack: 2.4.0 - ws: 8.13.0 + ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) transitivePeerDependencies: - bufferutil - encoding @@ -11097,6 +11109,10 @@ packages: '@turf/meta': 6.5.0 dev: true + /@tweenjs/tween.js@21.0.0: + resolution: {integrity: sha512-qVfOiFh0U8ZSkLgA6tf7kj2MciqRbSCWaJZRwftVO7UbtVDNsZAXpWXqvCDtIefvjC83UJB+vHTDOGm5ibXjEA==} + dev: false + /@types/acorn@4.0.6: resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} dependencies: @@ -12025,6 +12041,15 @@ packages: '@xtuc/long': 4.2.2 dev: true + /@welldone-software/why-did-you-render@7.0.1(react@18.2.0): + resolution: {integrity: sha512-Qe/8Xxa2G+LMdI6VoazescPzjjkHYduCDa8aHOJR50e9Bgs8ihkfMBY+ev7B4oc3N59Zm547Sgjf8h5y0FOyoA==} + peerDependencies: + react: ^16 || ^17 || ^18 || 18 + dependencies: + lodash: 4.17.21 + react: 18.2.0 + dev: true + /@xmldom/xmldom@0.8.7: resolution: {integrity: sha512-sI1Ly2cODlWStkINzqGrZ8K6n+MTSbAeQnAipGyL+KZCXuHaRlj2gyyy8B/9MvsFFqN7XHryQnB2QwhzvJXovg==} engines: {node: '>=10.0.0'} @@ -13076,7 +13101,6 @@ packages: requiresBuild: true dependencies: node-gyp-build: 4.6.0 - dev: false /bufrw@1.3.0: resolution: {integrity: sha512-jzQnSbdJqhIltU9O5KUiTtljP9ccw2u5ix59McQy4pV2xGhVLhRZIndY8GIrgh5HjXa6+QJ9AQhOd2QWQizJFQ==} @@ -20314,7 +20338,6 @@ packages: /node-gyp-build@4.6.0: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} hasBin: true - dev: false /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -22144,6 +22167,16 @@ packages: react: 18.2.0 dev: false + /react-hook-tracer@1.4.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-RX3JFlDmw5Q8LWltp0XRtMQ2VgaZF0QeuL4IfNwAA2eG4ASF7qaxBu7UOMGk9wrZB0NC6zV4R63osRas5ZrQGA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /react-i18next@13.0.3(i18next@23.4.4)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-/t4kt4Y2o+21hbvx+o9zpVnmoiud7KLDncyZFGN0U6TGAWYaXdTsp/ytAHFcKKSAODg4noIMaOO3X7bMgCqLHw==} peerDependencies: @@ -25043,7 +25076,6 @@ packages: requiresBuild: true dependencies: node-gyp-build: 4.6.0 - dev: false /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -25580,19 +25612,6 @@ packages: optional: true dev: true - /ws@8.13.0: - resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: true - /ws@8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3): resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} engines: {node: '>=10.0.0'} @@ -25607,7 +25626,6 @@ packages: dependencies: bufferutil: 4.0.7 utf-8-validate: 6.0.3 - dev: false /xdg-basedir@5.1.0: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} From dce7ee6aa7c1f363d2437860acd6454e8f97728d Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Thu, 10 Aug 2023 23:05:06 -0400 Subject: [PATCH 51/63] i18n types --- apps/app/src/utils/i18n.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/app/src/utils/i18n.ts b/apps/app/src/utils/i18n.ts index f9c7c0ed87..fbba3c5059 100644 --- a/apps/app/src/utils/i18n.ts +++ b/apps/app/src/utils/i18n.ts @@ -1,15 +1,19 @@ -import { type Namespace } from 'i18next' -// eslint-disable-next-line no-restricted-imports +/* eslint-disable no-restricted-imports */ +import { type DefaultNamespace } from 'i18next' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { type LiteralUnion } from 'type-fest' + +import { type Namespaces } from '@weareinreach/db/generated/namespaces' import i18nextConfig from '../../next-i18next.config.mjs' // type ArrayElementOrSelf = T extends Array ? U[] : T[] +type Namespace = LiteralUnion type NamespaceSSR = string | string[] | undefined export const getServerSideTranslations = async ( locale = 'en', - namespacesRequired?: Namespace, + namespacesRequired: Namespace | Namespace[] = i18nextConfig.defaultNS as DefaultNamespace, extraLocales?: string[] | false ) => serverSideTranslations(locale, namespacesRequired as NamespaceSSR, i18nextConfig, extraLocales) From 9fb25c4ea0e831126c5eb89beaad45cf6f3e33dd Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Thu, 10 Aug 2023 23:06:23 -0400 Subject: [PATCH 52/63] add wdyr & standalone react devtools --- packages/ui/.storybook/decorators.tsx | 9 +++++++++ packages/ui/.storybook/main.ts | 4 ++++ packages/ui/.storybook/preview.tsx | 16 ++++++++++++++-- packages/ui/.storybook/wdyr.ts | 7 +++++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 packages/ui/.storybook/wdyr.ts diff --git a/packages/ui/.storybook/decorators.tsx b/packages/ui/.storybook/decorators.tsx index 845ff8f551..5d9f303a42 100644 --- a/packages/ui/.storybook/decorators.tsx +++ b/packages/ui/.storybook/decorators.tsx @@ -158,3 +158,12 @@ export const WithSearchState = (Story: StoryFn, { parameters }: StoryContext) => ) WithSearchState.displayName = 'SearchStateProvider' + +export const WithWhyDidYouRender = (Story: StoryFn, { parameters, component }: StoryContext) => { + const { wdyr } = parameters + if (wdyr && component) { + // @ts-expect-error Module augmentation is too complex. + component.whyDidYouRender = wdyr + } + return +} diff --git a/packages/ui/.storybook/main.ts b/packages/ui/.storybook/main.ts index 8763a0fc05..34feb547b7 100644 --- a/packages/ui/.storybook/main.ts +++ b/packages/ui/.storybook/main.ts @@ -69,6 +69,10 @@ const config: StorybookConfig = { }, }, }, + previewHead: (head) => ` + + ${head} + `, webpackFinal: (config, options) => { const configAdditions: typeof config = { resolve: { diff --git a/packages/ui/.storybook/preview.tsx b/packages/ui/.storybook/preview.tsx index b54c09f479..013c845d0e 100644 --- a/packages/ui/.storybook/preview.tsx +++ b/packages/ui/.storybook/preview.tsx @@ -1,8 +1,10 @@ +import './wdyr' import { type BADGE } from '@geometricpanda/storybook-addon-badges' import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport' import { type Preview } from '@storybook/react' +import { type WhyDidYouRenderOptions } from '@welldone-software/why-did-you-render' import { type RequestHandler, rest } from 'msw' -import { initialize as initializeMsw, /*mswDecorator,*/ mswLoader } from 'msw-storybook-addon' +import { initialize as initializeMsw, mswLoader } from 'msw-storybook-addon' import { type BaseRouter } from 'next/dist/shared/lib/router/router' import { type Router } from 'next/router' @@ -17,6 +19,7 @@ import { WithSearchState, WithStrictMode, WithTRPC, + WithWhyDidYouRender, } from './decorators' import { i18n } from './i18next' import { type Viewports } from './types' @@ -89,7 +92,15 @@ const preview: Preview = { }, pseudo: {}, }, - decorators: [WithSearchState, Layouts, WithMantine, WithI18n, /*mswDecorator,*/ WithTRPC, WithStrictMode], + decorators: [ + WithSearchState, + Layouts, + WithMantine, + WithI18n, + WithTRPC, + WithStrictMode, + WithWhyDidYouRender, + ], loaders: [mswLoader], } export default preview @@ -123,6 +134,7 @@ declare module '@storybook/react' { pseudo?: Partial> & { rootElement?: string } rqDevtools?: boolean searchContext?: SearchStateProviderProps['initState'] + wdyr?: boolean | WhyDidYouRenderOptions } } type PseudoStates = diff --git a/packages/ui/.storybook/wdyr.ts b/packages/ui/.storybook/wdyr.ts new file mode 100644 index 0000000000..66ef5a0a19 --- /dev/null +++ b/packages/ui/.storybook/wdyr.ts @@ -0,0 +1,7 @@ +import whyDidYouRender from '@welldone-software/why-did-you-render' +import React from 'react' + +// eslint-disable-next-line node/no-process-env +if (process.env.NODE_ENV === 'development') { + whyDidYouRender(React) +} From 0fc69ec9f24f99932eb31fd6326e6c2de5de0c44 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Fri, 11 Aug 2023 11:38:46 -0400 Subject: [PATCH 53/63] change query to fix types --- apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx b/apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx index 91c34e0dc2..f591dc6d11 100644 --- a/apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx +++ b/apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx @@ -29,7 +29,7 @@ const OrgLocationPage: NextPage = () => { const { data: orgData, status: orgDataStatus } = api.organization.getBySlug.useQuery(query, { enabled: router.isReady, }) - const { data, status } = api.location.getById.useQuery({ id: orgLocationId }) + const { data, status } = api.location.forLocationPage.useQuery({ id: orgLocationId }) const { data: isSaved } = api.savedList.isSaved.useQuery(orgData?.id as string, { enabled: orgDataStatus === 'success' && Boolean(orgData?.id), }) From c1f6f287e8dc078f5d4077325fef4fa2c18f0679 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Fri, 11 Aug 2023 11:59:35 -0400 Subject: [PATCH 54/63] update mockdata --- packages/ui/mockData/fieldOpt.ts | 9 ++++++--- packages/ui/mockData/location.ts | 5 +++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/ui/mockData/fieldOpt.ts b/packages/ui/mockData/fieldOpt.ts index 92f6e58cda..f562715b96 100644 --- a/packages/ui/mockData/fieldOpt.ts +++ b/packages/ui/mockData/fieldOpt.ts @@ -1,10 +1,11 @@ import { z } from 'zod' +import fs from 'fs' +import path from 'path' + import { type ApiInput, type ApiOutput } from '@weareinreach/api' import { getTRPCMock } from '~ui/lib/getTrpcMock' -import countryGovDistMapData from './json/countryGovDistMap.json' - export const attributeCategories = [ { id: 'attc_01GW2HHFV3DJ380F351SKB0B74', @@ -9215,7 +9216,9 @@ const countryGovDistMapSchema = z .array() const countryGovDistMap = new Map( - countryGovDistMapSchema.parse(countryGovDistMapData) + countryGovDistMapSchema.parse( + JSON.parse(fs.readFileSync(path.resolve(__dirname, './json/countryGovDistMap.json'), 'utf-8')) + ) ) export const fieldOpt = { attributeCategories: getTRPCMock({ diff --git a/packages/ui/mockData/location.ts b/packages/ui/mockData/location.ts index 59194e6537..6b9c4ff79e 100644 --- a/packages/ui/mockData/location.ts +++ b/packages/ui/mockData/location.ts @@ -1,4 +1,3 @@ -import { type ApiOutput } from '@weareinreach/api' import { getTRPCMock, type MockDataObject, type MockHandlerObject } from '~ui/lib/getTrpcMock' const locationData = { @@ -33,9 +32,11 @@ const locationData = { id: 'oloc_01GVH3VEVBERFNA9PHHJYEBGA3', name: 'Whitman-Walker 1525', street1: '1525 14th St. NW ', - street2: '', + street2: null, city: 'Washington', postCode: '20005', + latitude: 38.91, + longitude: -77.032, country: 'US', govDist: { abbrev: 'DC', From 2e5270410aad468b7f74f078cbed90be4da8f17b Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Fri, 11 Aug 2023 21:34:41 -0400 Subject: [PATCH 55/63] deps --- packages/ui/package.json | 1 + pnpm-lock.yaml | 58 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 8033532aec..6f01bf3220 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -28,6 +28,7 @@ "@tweenjs/tween.js": "21.0.0", "@weareinreach/env": "workspace:*", "@weareinreach/util": "workspace:*", + "ahooks": "3.7.8", "ajv": "8.12.0", "alex": "11.0.0", "cookies-next": "2.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e51e90c91..f388eafd22 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1385,6 +1385,9 @@ importers: '@weareinreach/util': specifier: workspace:* version: link:../util + ahooks: + specifier: 3.7.8 + version: 3.7.8(react@18.2.0) ajv: specifier: 8.12.0 version: 8.12.0 @@ -6370,7 +6373,7 @@ packages: dependencies: '@mantine/ssr': 6.0.18(@emotion/react@11.11.1)(@emotion/server@11.11.0)(react-dom@18.2.0)(react@18.2.0) '@mantine/styles': 6.0.18(@emotion/react@11.11.1)(react-dom@18.2.0)(react@18.2.0) - next: 13.4.13(@babel/core@7.22.10)(react-dom@18.2.0)(react@18.2.0) + next: 13.4.13(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) transitivePeerDependencies: @@ -11002,7 +11005,7 @@ packages: '@trpc/client': 10.37.1(@trpc/server@10.37.1) '@trpc/react-query': 10.37.1(@tanstack/react-query@4.32.6)(@trpc/client@10.37.1)(@trpc/server@10.37.1)(react-dom@18.2.0)(react@18.2.0) '@trpc/server': 10.37.1 - next: 13.4.13(@babel/core@7.22.10)(react-dom@18.2.0)(react@18.2.0) + next: 13.4.13(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-ssr-prepass: 1.5.0(react@18.2.0) @@ -11373,6 +11376,10 @@ packages: pretty-format: 29.5.0 dev: true + /@types/js-cookie@2.2.7: + resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} + dev: false + /@types/js-levenshtein@1.1.1: resolution: {integrity: sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==} dev: true @@ -12204,6 +12211,29 @@ packages: clean-stack: 2.2.0 indent-string: 4.0.0 + /ahooks-v3-count@1.0.0: + resolution: {integrity: sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ==} + dev: false + + /ahooks@3.7.8(react@18.2.0): + resolution: {integrity: sha512-e/NMlQWoCjaUtncNFIZk3FG1ImSkV/JhScQSkTqnftakRwdfZWSw6zzoWSG9OMYqPNs2MguDYBUFFC6THelWXA==} + engines: {node: '>=8.0.0'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || 18 + dependencies: + '@babel/runtime': 7.22.6 + '@types/js-cookie': 2.2.7 + ahooks-v3-count: 1.0.0 + dayjs: 1.11.9 + intersection-observer: 0.12.2 + js-cookie: 2.2.1 + lodash: 4.17.21 + react: 18.2.0 + resize-observer-polyfill: 1.5.1 + screenfull: 5.2.0 + tslib: 2.5.0 + dev: false + /ajv-formats@2.1.1(ajv@8.12.0): resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -17108,6 +17138,10 @@ packages: engines: {node: '>= 0.10'} dev: true + /intersection-observer@0.12.2: + resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} + dev: false + /invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} dependencies: @@ -18312,6 +18346,10 @@ packages: resolution: {integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==} dev: true + /js-cookie@2.2.1: + resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} + dev: false + /js-levenshtein@1.1.6: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} engines: {node: '>=0.10.0'} @@ -20107,7 +20145,7 @@ packages: '@panva/hkdf': 1.0.4 cookie: 0.5.0 jose: 4.13.2 - next: 13.4.13(@babel/core@7.22.10)(react-dom@18.2.0)(react@18.2.0) + next: 13.4.13(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) oauth: 0.9.15 openid-client: 5.4.0 preact: 10.13.2 @@ -20131,7 +20169,7 @@ packages: hoist-non-react-statics: 3.3.2 i18next: 23.4.4 i18next-fs-backend: 2.1.5 - next: 13.4.13(@babel/core@7.22.10)(react-dom@18.2.0)(react@18.2.0) + next: 13.4.13(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-i18next: 13.0.3(i18next@23.4.4)(react-dom@18.2.0)(react@18.2.0) @@ -20195,6 +20233,7 @@ packages: transitivePeerDependencies: - '@babel/core' - babel-plugin-macros + dev: true /next@13.4.13(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-A3YVbVDNeXLhWsZ8Nf6IkxmNlmTNz0yVg186NJ97tGZqPDdPzTrHotJ+A1cuJm2XfuWPrKOUZILl5iBQkIf8Jw==} @@ -20243,7 +20282,7 @@ packages: next: '*' dependencies: chokidar: 3.5.3 - next: 13.4.13(@babel/core@7.22.10)(react-dom@18.2.0)(react@18.2.0) + next: 13.4.13(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) dev: false /nice-try@1.0.5: @@ -22766,6 +22805,10 @@ packages: engines: {node: '>=0.10.5'} dev: true + /resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + dev: false + /resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} @@ -23096,6 +23139,11 @@ packages: ajv-keywords: 5.1.0(ajv@8.12.0) dev: true + /screenfull@5.2.0: + resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} + engines: {node: '>=0.10.0'} + dev: false + /sembear@0.5.2: resolution: {integrity: sha512-Ij1vCAdFgWABd7zTg50Xw1/p0JgESNxuLlneEAsmBrKishA06ulTTL/SHGmNy2Zud7+rKrHTKNI6moJsn1ppAQ==} dependencies: From 0c9f5ae9a0405d3810390a2fd18a204bec048eaf Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Fri, 11 Aug 2023 21:36:28 -0400 Subject: [PATCH 56/63] google maps related api handlers --- .../location/query.forGoogleMaps.handler.ts | 67 ++++++++++++------- .../location/query.forGoogleMaps.schema.ts | 6 +- .../location/query.forLocationCard.handler.ts | 2 + .../location/query.forVisitCard.handler.ts | 3 + 4 files changed, 51 insertions(+), 27 deletions(-) diff --git a/packages/api/router/location/query.forGoogleMaps.handler.ts b/packages/api/router/location/query.forGoogleMaps.handler.ts index d0957a8c10..136aa83b2b 100644 --- a/packages/api/router/location/query.forGoogleMaps.handler.ts +++ b/packages/api/router/location/query.forGoogleMaps.handler.ts @@ -1,42 +1,59 @@ import { TRPCError } from '@trpc/server' +import { getBounds, getCenterOfBounds } from 'geolib' import { prisma } from '@weareinreach/db' -import { handleError } from '~api/lib/errorHandler' import { globalWhere } from '~api/selects/global' import { type TRPCHandlerParams } from '~api/types/handler' import { type TForGoogleMapsSchema } from './query.forGoogleMaps.schema' +const getBoundary = (coords: { latitude: number; longitude: number }[]): google.maps.LatLngBoundsLiteral => { + const bounds = getBounds(coords) + return { + north: bounds.maxLat, + east: bounds.maxLng, + south: bounds.minLat, + west: bounds.minLng, + } +} +const getCenter = (coords: { latitude: number; longitude: number }[]): google.maps.LatLngLiteral => { + const center = getCenterOfBounds(coords) + return { lat: center.latitude, lng: center.longitude } +} + export const forGoogleMaps = async ({ input }: TRPCHandlerParams) => { - try { - const select = { + const result = await prisma.orgLocation.findMany({ + where: { + ...globalWhere.isPublic(), + id: { in: Array.isArray(input.locationIds) ? input.locationIds : [input.locationIds] }, + AND: [{ latitude: { not: 0 } }, { longitude: { not: 0 } }], + }, + select: { id: true, name: true, latitude: true, longitude: true, - } + }, + }) + if (!result.length) { + throw new TRPCError({ code: 'NOT_FOUND' }) + } + const coordsForBounds: { latitude: number; longitude: number }[] = [] - const result = Array.isArray(input) - ? await prisma.orgLocation.findMany({ - where: { - ...globalWhere.isPublic(), - id: { in: input }, - }, - select, - }) - : await prisma.orgLocation.findUniqueOrThrow({ - where: { - ...globalWhere.isPublic(), - id: input, - }, - select, - }) + for (const { latitude, longitude } of result) { + if (latitude && longitude) coordsForBounds.push({ latitude, longitude }) + } + const bounds = result.length > 1 ? getBoundary(coordsForBounds) : null + const center = + result.length === 1 && result.at(0)?.latitude && result.at(0)?.longitude + ? ({ lat: result.at(0)!.latitude, lng: result.at(0)!.longitude } as { lat: number; lng: number }) + : getCenter(coordsForBounds) + const zoom = result.length === 1 ? 11 : null - if (Array.isArray(input) && Array.isArray(result) && !result.length) { - throw new TRPCError({ code: 'NOT_FOUND' }) - } - return result - } catch (error) { - handleError(error) + return { + locations: result, + bounds, + center, + zoom, } } diff --git a/packages/api/router/location/query.forGoogleMaps.schema.ts b/packages/api/router/location/query.forGoogleMaps.schema.ts index 03caab8287..7de195c404 100644 --- a/packages/api/router/location/query.forGoogleMaps.schema.ts +++ b/packages/api/router/location/query.forGoogleMaps.schema.ts @@ -1,6 +1,8 @@ -import { type z } from 'zod' +import { z } from 'zod' import { prefixedId } from '~api/schemas/idPrefix' -export const ZForGoogleMapsSchema = prefixedId('orgLocation').or(prefixedId('orgLocation').array()) +export const ZForGoogleMapsSchema = z.object({ + locationIds: prefixedId('orgLocation').array(), +}) export type TForGoogleMapsSchema = z.infer diff --git a/packages/api/router/location/query.forLocationCard.handler.ts b/packages/api/router/location/query.forLocationCard.handler.ts index a190e22164..08b7d52f20 100644 --- a/packages/api/router/location/query.forLocationCard.handler.ts +++ b/packages/api/router/location/query.forLocationCard.handler.ts @@ -17,6 +17,8 @@ export const forLocationCard = async ({ input }: TRPCHandlerParams Date: Fri, 11 Aug 2023 21:40:04 -0400 Subject: [PATCH 57/63] fix/refactor GoogleMaps component --- .../ui/components/core/GoogleMap.stories.tsx | 147 ++++++++++++++++++ packages/ui/components/core/GoogleMap.tsx | 138 ++++++++-------- packages/ui/hooks/useGoogleMapMarker.tsx | 87 +++++++++++ packages/ui/hooks/useGoogleMaps.ts | 57 +++++++ packages/ui/providers/GoogleMaps.tsx | 72 +++++++++ 5 files changed, 434 insertions(+), 67 deletions(-) create mode 100644 packages/ui/components/core/GoogleMap.stories.tsx create mode 100644 packages/ui/hooks/useGoogleMapMarker.tsx create mode 100644 packages/ui/hooks/useGoogleMaps.ts create mode 100644 packages/ui/providers/GoogleMaps.tsx diff --git a/packages/ui/components/core/GoogleMap.stories.tsx b/packages/ui/components/core/GoogleMap.stories.tsx new file mode 100644 index 0000000000..4a0f3abfad --- /dev/null +++ b/packages/ui/components/core/GoogleMap.stories.tsx @@ -0,0 +1,147 @@ +import { action } from '@storybook/addon-actions' +import { type Meta, type StoryObj } from '@storybook/react' +import { useEffect } from 'react' + +import { useGoogleMapMarker } from '~ui/hooks/useGoogleMapMarker' +import { useGoogleMaps } from '~ui/hooks/useGoogleMaps' +import { getTRPCMock } from '~ui/lib/getTrpcMock' +import { trpc as api } from '~ui/lib/trpcClient' +import { GoogleMapsProvider } from '~ui/providers/GoogleMaps' + +import { GoogleMap, type GoogleMapProps } from './GoogleMap' + +const MapWithMarkers = ({ locationIds, height, width }: GoogleMapProps) => { + const mapMarker = useGoogleMapMarker() + const { map, mapIsReady } = useGoogleMaps() + const actionLogger = action('Map marker ->') + const { data, isLoading } = api.location.forGoogleMaps.useQuery( + { locationIds: Array.isArray(locationIds) ? locationIds : [locationIds] }, + { enabled: !!map && !!mapMarker } + ) + useEffect(() => { + if (!isLoading && data && mapIsReady) { + const markers: google.maps.Marker[] = [] + for (const location of data.locations) { + actionLogger({ + id: location.id, + name: location.name ?? '', + lat: location.latitude ?? 0, + lng: location.longitude ?? 0, + }) + const newMarker = mapMarker.add({ + map, + id: location.id, + name: location.name ?? '', + lat: location.latitude ?? 0, + lng: location.longitude ?? 0, + }) + markers.push(newMarker) + } + return () => { + for (const marker of markers) mapMarker.remove(marker) + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoading, data, mapIsReady]) + + return +} + +export default { + title: 'Design System/Google Map', + component: MapWithMarkers, + args: { + width: 500, + height: 500, + }, + parameters: { + // layoutWrapper: 'gridSingle', + wdyr: { trackAllPureComponents: true }, + }, + decorators: [ + (Story) => ( + + + + ), + ], +} satisfies Meta + +type StoryDef = StoryObj +export const SingleLocation = { + parameters: { + msw: [ + getTRPCMock({ + path: ['location', 'forGoogleMaps'], + response: { + locations: [ + { + id: 'oloc_01GVH3VEVBERFNA9PHHJYEBGA3', + name: 'Whitman-Walker 1525', + latitude: 38.91, + longitude: -77.032, + }, + ], + bounds: null, + center: { + lat: 38.91, + lng: -77.032, + }, + zoom: 13, + }, + }), + ], + }, + args: { + locationIds: 'oloc_01GVH3VEVBERFNA9PHHJYEBGA3', + }, +} satisfies StoryDef +export const MultipleLocations = { + args: { + locationIds: [ + 'oloc_01GVH3VFDP0W49EXHYR31N862K', + 'oloc_01GVH3VFDPV2KQYZ3HWX4DABQJ', + 'oloc_01GVH3VFDPYPNQDWZEGHW9QWM9', + ], + }, + parameters: { + msw: [ + getTRPCMock({ + path: ['location', 'forGoogleMaps'], + response: { + locations: [ + { + id: 'oloc_01GVH3VFDP0W49EXHYR31N862K', + name: 'Los Angeles Office', + latitude: 34.062, + longitude: -118.306, + }, + { + id: 'oloc_01GVH3VFDPV2KQYZ3HWX4DABQJ', + name: 'New York City Office', + latitude: 40.705, + longitude: -74.011, + }, + { + id: 'oloc_01GVH3VFDPYPNQDWZEGHW9QWM9', + name: 'Washington, D.C. Office', + latitude: 38.907, + longitude: -77.037, + }, + ], + bounds: { + north: 40.705, + east: -74.011, + south: 34.062, + west: -118.306, + }, + center: { + lat: 37.3835, + lng: -96.1585, + }, + zoom: null, + }, + }), + ], + }, +} satisfies StoryDef diff --git a/packages/ui/components/core/GoogleMap.tsx b/packages/ui/components/core/GoogleMap.tsx index f4dc37ce23..e132bcad26 100644 --- a/packages/ui/components/core/GoogleMap.tsx +++ b/packages/ui/components/core/GoogleMap.tsx @@ -1,89 +1,93 @@ /* eslint-disable turbo/no-undeclared-env-vars */ /* eslint-disable node/no-process-env */ +import { Status, Wrapper } from '@googlemaps/react-wrapper' import { rem, Skeleton } from '@mantine/core' -import { GoogleMap as GMap, Marker, type MarkerProps, useJsApiLoader } from '@react-google-maps/api' -import { getBounds } from 'geolib' -import { memo, useCallback } from 'react' +import { memo, useEffect, useRef } from 'react' +import { useGoogleMaps, useGoogleMapSetup } from '~ui/hooks/useGoogleMaps' import { trpc as api } from '~ui/lib/trpcClient' -export const GoogleMapComponent = ({ height, width, locationIds }: GoogleMapProps) => { - const { data, isLoading } = api.location.forGoogleMaps.useQuery(locationIds) +const MapRenderer = memo(({ height, width }: MapRendererProps) => { + const { setMap, setInfoWindow, mapEvents } = useGoogleMapSetup() + const mapRef = useRef(null) - const { isLoaded } = useJsApiLoader({ - googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API as string, - }) - const getBoundProps = () => { - if (Array.isArray(data)) { - const coords: { latitude: number; longitude: number }[] = [] - for (const { latitude, longitude } of data) { - if (!latitude || !longitude) continue - coords.push({ longitude, latitude }) - } - const bounds = getBounds(coords) - if (isLoaded) { - return new google.maps.LatLngBounds( - { lat: bounds.minLat, lng: bounds.minLng }, - { lat: bounds.maxLat, lng: bounds.minLng } - ) - } - } - throw new Error('Must have multiple points', { cause: { data, isLoading, isLoaded } }) - } - - const getMarkers = () => { - if (Array.isArray(data)) { - return data.map(({ latitude, longitude }, idx) => { - if (!latitude || !longitude) return null - const props: MarkerProps = { - position: { - lat: latitude, - lng: longitude, - }, - } - return + useEffect(() => { + if (mapRef.current) { + const newMap = new google.maps.Map(mapRef.current, { + disableDefaultUI: true, + mapId: 'cd9fc3a944b18418', + isFractionalZoomEnabled: true, }) + const newInfoWindow = new google.maps.InfoWindow() + setMap(newMap) + setInfoWindow(newInfoWindow) + mapEvents.ready.emit(true) } - if (!data?.latitude || !data?.longitude) return null - const props: MarkerProps = { - position: { - lat: data.latitude, - lng: data.longitude, - }, - } - return - } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return
+}) + +MapRenderer.displayName = 'GoogleMapRenderer' +export const GoogleMap = ({ height, width, locationIds }: GoogleMapProps) => { + const { map, mapIsReady, mapEvents, camera } = useGoogleMaps() + const { data, isLoading } = api.location.forGoogleMaps.useQuery( + { locationIds: Array.isArray(locationIds) ? locationIds : [locationIds] }, + { enabled: mapIsReady } + ) - // const RenderMap = () => { - const onLoad = useCallback( - function onLoad(map: google.maps.Map) { - map.setOptions({ disableDefaultUI: true, keyboardShortcuts: false }) - if (isLoading) return - if (Array.isArray(data)) { - map.fitBounds(getBoundProps()) - } else if (data?.latitude && data?.longitude && isLoaded) { - const center = new google.maps.LatLng({ lat: data.latitude, lng: data.longitude }) + useEffect(() => { + if (mapIsReady && !isLoading && data) { + const { bounds, center, zoom } = data + if (zoom) { + map.setZoom(zoom) + } + if (center) { map.setCenter(center) - map.setZoom(11) } - }, + if (bounds) { + map.fitBounds(bounds) + map.panToBounds(bounds) + } + camera.center = center + camera.zoom = zoom ?? map.getZoom() + mapEvents.initialPropsSet.emit(true) + } // eslint-disable-next-line react-hooks/exhaustive-deps - [isLoading] - ) + }, [mapIsReady, map, isLoading, data]) - return isLoaded && !isLoading ? ( - - {getMarkers()} - - ) : ( - + const mapRender = (status: Status) => { + switch (status) { + case Status.LOADING: { + return + } + case Status.FAILURE: { + return <> + } + case Status.SUCCESS: { + return + } + } + } + + return ( + ) } -export const GoogleMap = memo(GoogleMapComponent) +GoogleMap.displayName = '@weareinreach/ui/components/core/GoogleMap' -interface GoogleMapProps { +export interface GoogleMapProps { locationIds: string | string[] height: number width: number } + +type MapRendererProps = Omit diff --git a/packages/ui/hooks/useGoogleMapMarker.tsx b/packages/ui/hooks/useGoogleMapMarker.tsx new file mode 100644 index 0000000000..44394f953b --- /dev/null +++ b/packages/ui/hooks/useGoogleMapMarker.tsx @@ -0,0 +1,87 @@ +import { MantineProvider, Stack, Text, Title } from '@mantine/core' +import { useRouter } from 'next/router' +import { type Route } from 'nextjs-routes' +import { createRoot } from 'react-dom/client' + +import { Link } from '~ui/components/core/Link' +import { useCustomVariant } from '~ui/hooks/useCustomVariant' +import { useGoogleMaps } from '~ui/hooks/useGoogleMaps' +import { appCache, appTheme } from '~ui/theme' + +const getHref: GetHref = ({ slug, locationId }) => { + if (slug && locationId) { + return { + pathname: '/org/[slug]/[orgLocationId]', + query: { slug, orgLocationId: locationId }, + } + } + return { + pathname: '/org/[slug]', + query: { slug }, + } +} +type SlugOnly = { + slug: string + locationId?: undefined +} +type LocationAndSlug = { + slug: string + locationId: string +} +type GetHref = (params: SlugOnly | LocationAndSlug) => Route +export const useGoogleMapMarker = () => { + const router = useRouter() + const variant = useCustomVariant() + const { mapIsReady, infoWindow } = useGoogleMaps() + return { + add({ id, lat, lng, name, address, slug, locationId, map }: AddMarkerParams) { + if (!mapIsReady) throw new Error('map is not ready') + const position = new google.maps.LatLng({ lat, lng }) + const marker = new google.maps.Marker({ position, map }) + + const infoBoxNode = document.createElement('div') + infoBoxNode.id = id + const infoBoxContent = createRoot(infoBoxNode) + + infoBoxContent.render( + + + {slug ? ( + router.push(getHref({ slug, locationId }))} + > + {name} + + ) : ( + {name} + )} + {address && {Array.isArray(address) ? address.join('\n') : address}} + + + ) + if (mapIsReady) { + marker.addListener('click', () => { + infoWindow.setContent(infoBoxNode) + infoWindow.open(map, marker) + }) + } + return marker + }, + remove(marker: google.maps.Marker) { + marker.setMap(null) + google.maps.event.clearInstanceListeners(marker) + }, + } +} + +interface AddMarkerParams { + id: string + lat: number + lng: number + name: string + address?: string | string[] + slug?: string + locationId?: string + map: google.maps.Map +} diff --git a/packages/ui/hooks/useGoogleMaps.ts b/packages/ui/hooks/useGoogleMaps.ts new file mode 100644 index 0000000000..f10326c370 --- /dev/null +++ b/packages/ui/hooks/useGoogleMaps.ts @@ -0,0 +1,57 @@ +import { useContext } from 'react' + +import { GoogleMapContext, type MapEvents } from '~ui/providers/GoogleMaps' + +export const useGoogleMaps = (): UseGoogleMapsReturn => { + const context = useContext(GoogleMapContext) + if (!context) { + throw new Error('useGoogleMaps must be used within a GoogleMapsProvider') + } + if (context.map && context.isReady && context.infoWindow) { + return { + map: context.map, + infoWindow: context.infoWindow, + mapIsReady: true, + mapEvents: context.mapEvents, + camera: context.camera, + } + } else { + return { + map: undefined, + infoWindow: undefined, + mapIsReady: false, + mapEvents: context.mapEvents, + camera: context.camera, + } + } +} +export const useGoogleMapSetup = () => { + const context = useContext(GoogleMapContext) + if (!context) { + throw new Error('useGoogleMapSetup must be used within a GoogleMapsProvider') + } + return { + map: context.map, + setMap: context.setMap, + setInfoWindow: context.setInfoWindow, + mapEvents: context.mapEvents, + mapIsReady: context.isReady, + camera: context.camera, + } +} + +interface MapNotReady { + map: undefined + infoWindow: undefined + mapIsReady: false + mapEvents: MapEvents + camera: google.maps.CameraOptions +} +interface MapIsReady { + map: google.maps.Map + infoWindow: google.maps.InfoWindow + mapIsReady: true + mapEvents: MapEvents + camera: google.maps.CameraOptions +} +type UseGoogleMapsReturn = MapNotReady | MapIsReady diff --git a/packages/ui/providers/GoogleMaps.tsx b/packages/ui/providers/GoogleMaps.tsx new file mode 100644 index 0000000000..cdf70bbd01 --- /dev/null +++ b/packages/ui/providers/GoogleMaps.tsx @@ -0,0 +1,72 @@ +import { useEventEmitter } from 'ahooks' +import { type EventEmitter } from 'ahooks/lib/useEventEmitter' +import { createContext, type ReactNode, useEffect, useMemo, useRef, useState } from 'react' + +export const GoogleMapContext = createContext(null) +GoogleMapContext.displayName = 'GoogleMapContext' + +export const GoogleMapsProvider = ({ children }: { children: ReactNode }) => { + const [map, setMap] = useState() + const [infoWindow, setInfoWindow] = useState() + const [isReady, setIsReady] = useState(false) + + const mapEvents = { + ready: useEventEmitter(), + initialPropsSet: useEventEmitter(), + } + const mapEventRef = useRef(mapEvents) + const initialCamera = useMemo(() => { + return { + center: map?.getCenter()?.toJSON() ?? { lat: 39.8283, lng: -98.5795 }, + zoom: map?.getZoom() ?? 4, + } + }, [map]) + + const cameraRef = useRef(initialCamera) + + mapEventRef.current.ready.useSubscription((val) => { + setIsReady(val) + }) + + useEffect(() => { + if (map && infoWindow) { + const infoListener = google.maps.event.addListener(infoWindow, 'closeclick', () => { + if (cameraRef.current.center) map.panTo(cameraRef.current.center) + }) + const mapListener = google.maps.event.addListener(map, 'click', () => { + infoWindow.close() + }) + + return () => { + google.maps.event.removeListener(infoListener) + google.maps.event.removeListener(mapListener) + } + } + }, [map, infoWindow]) + const contextValue = { + map, + infoWindow, + setMap, + setInfoWindow, + isReady, + mapEvents, + camera: cameraRef.current, + } + + return {children} +} + +export interface MapEvents { + ready: EventEmitter + initialPropsSet: EventEmitter +} + +interface GoogleMapContextValue { + map: google.maps.Map | undefined + infoWindow: google.maps.InfoWindow | undefined + setMap: (map: google.maps.Map) => void + setInfoWindow: (infoWindow: google.maps.InfoWindow) => void + isReady: boolean + mapEvents: MapEvents + camera: google.maps.CameraOptions +} From a68efdf8266325471df12c559897e21584a832e6 Mon Sep 17 00:00:00 2001 From: Joe Karow <58997957+JoeKarow@users.noreply.github.com> Date: Fri, 11 Aug 2023 21:44:10 -0400 Subject: [PATCH 58/63] implement new GoogleMaps component --- apps/app/src/pages/_app.tsx | 15 ++- apps/app/src/pages/org/[slug]/index.tsx | 12 +- .../sections/LocationCard.stories.tsx | 10 +- .../ui/components/sections/LocationCard.tsx | 127 +++++++++++++++++- packages/ui/components/sections/VisitCard.tsx | 27 +++- packages/ui/hooks/useFormattedAddress.ts | 2 +- packages/ui/mockData/location.ts | 5 +- 7 files changed, 176 insertions(+), 22 deletions(-) diff --git a/apps/app/src/pages/_app.tsx b/apps/app/src/pages/_app.tsx index 52643e572d..3eb359e35f 100644 --- a/apps/app/src/pages/_app.tsx +++ b/apps/app/src/pages/_app.tsx @@ -17,6 +17,7 @@ import { Footer } from '@weareinreach/ui/components/sections/Footer' import { Navbar } from '@weareinreach/ui/components/sections/Navbar' import { useScreenSize } from '@weareinreach/ui/hooks/useScreenSize' import { BodyGrid } from '@weareinreach/ui/layouts/BodyGrid' +import { GoogleMapsProvider } from '@weareinreach/ui/providers/GoogleMaps' import { SearchStateProvider } from '@weareinreach/ui/providers/SearchState' import { appCache, appTheme } from '@weareinreach/ui/theme' import { api } from '~app/utils/api' @@ -88,12 +89,14 @@ const MyApp = (appProps: AppPropsWithGridSwitch) => { > - - - {PageContent} - {(isMobile || isTablet) && } -