diff --git a/.changeset/narrow-content-scope-value-types.md b/.changeset/narrow-content-scope-value-types.md new file mode 100644 index 00000000000..383b23ae3ec --- /dev/null +++ b/.changeset/narrow-content-scope-value-types.md @@ -0,0 +1,40 @@ +--- +"@dextinity/cms-api": major +"@dextinity/brevo-api": major +"@dextinity/cms-admin": major +"@dextinity/site-nextjs": major +--- + +Restrict content scope values to `string | number | null | undefined` + +The content scope interfaces accepted values of any type (`Record`, `[key: string]: unknown`), which hid mistakes such as passing a scope class instead of a scope instance, or wrapping a scope in another object. +Scope dimensions are now typed as `string | number | null | undefined`: + +- `ScopeInterface` (page tree), `RedirectScopeInterface` and `DamScopeInterface` in `@dextinity/cms-api` +- `EmailCampaignScopeInterface` in `@dextinity/brevo-api` +- `ContentScope` in `@dextinity/cms-admin` +- `scope` in the preview params returned by `previewParams()`, `legacyPagesRouterPreviewParams()` and `setSitePreviewParams()` in `@dextinity/site-nextjs` + +**Migration** + +TypeScript only gives classes an index signature when it is declared explicitly, so scope classes in your application need one. +It can be narrower than the interface, so a scope with only string dimensions declares `[key: string]: string`: + +```ts +@Embeddable() +@ObjectType("PageTreeNodeScope") +@InputType("PageTreeNodeScopeInput") +export class PageTreeNodeScope { + [key: string]: string; + + @Property({ columnType: "text" }) + @Field() + @IsString() + domain: string; + + @Property({ columnType: "text" }) + @Field() + @IsString() + language: string; +} +``` diff --git a/demo/api/src/brevo/brevo-contact/dto/brevo-contact-subscribe.scope.ts b/demo/api/src/brevo/brevo-contact/dto/brevo-contact-subscribe.scope.ts index 513d44d1bdc..8c713bfdf31 100644 --- a/demo/api/src/brevo/brevo-contact/dto/brevo-contact-subscribe.scope.ts +++ b/demo/api/src/brevo/brevo-contact/dto/brevo-contact-subscribe.scope.ts @@ -1,6 +1,8 @@ import { IsString, MaxLength } from "class-validator"; export class EmailContactSubscribeScope { + [key: string]: string; + @IsString() @MaxLength(64) domain: string; diff --git a/demo/api/src/brevo/email-campaign/email-campaign-content-scope.ts b/demo/api/src/brevo/email-campaign/email-campaign-content-scope.ts index c1180c6a33e..ebf5ad6fc9e 100644 --- a/demo/api/src/brevo/email-campaign/email-campaign-content-scope.ts +++ b/demo/api/src/brevo/email-campaign/email-campaign-content-scope.ts @@ -6,6 +6,8 @@ import { IsString } from "class-validator"; @ObjectType() @InputType("EmailCampaignContentScopeInput") export class EmailCampaignContentScope { + [key: string]: string; + @Property({ columnType: "text" }) @Field() @IsString() diff --git a/demo/api/src/dam/dto/dam-scope.ts b/demo/api/src/dam/dto/dam-scope.ts index e1f3a664cc6..93217bcdb16 100644 --- a/demo/api/src/dam/dto/dam-scope.ts +++ b/demo/api/src/dam/dto/dam-scope.ts @@ -6,6 +6,8 @@ import { IsString } from "class-validator"; @ObjectType() @InputType("DamScopeInput") export class DamScope { + [key: string]: string; + @Property({ columnType: "text" }) @Field() @IsString() diff --git a/demo/api/src/news/entities/news.entity.ts b/demo/api/src/news/entities/news.entity.ts index 954cf025ea0..5322c997a99 100644 --- a/demo/api/src/news/entities/news.entity.ts +++ b/demo/api/src/news/entities/news.entity.ts @@ -51,6 +51,8 @@ registerEnumType(NewsCategory, { @ObjectType("") @InputType("NewsContentScopeInput") export class NewsContentScope { + [key: string]: string; + @Property({ columnType: "text" }) @Field() @IsString() diff --git a/demo/api/src/page-tree/dto/page-tree-node-scope.ts b/demo/api/src/page-tree/dto/page-tree-node-scope.ts index 644c478a6e6..24a704fd556 100644 --- a/demo/api/src/page-tree/dto/page-tree-node-scope.ts +++ b/demo/api/src/page-tree/dto/page-tree-node-scope.ts @@ -7,6 +7,8 @@ import { IsString } from "class-validator"; @InputType("PageTreeNodeScopeInput") // name must not be changed in the app // @TODO: disguise @ObjectType("PageTreeContentScope") and @InputType("PageTreeContentScopeInput") decorators under a custom decorator: f.i. @PageTreeNodeScope export class PageTreeNodeScope { + [key: string]: string; + @Property({ columnType: "text" }) @Field() @IsString() diff --git a/demo/api/src/redirects/dto/redirect-scope.ts b/demo/api/src/redirects/dto/redirect-scope.ts index 031878aaed7..9acfb771ce2 100644 --- a/demo/api/src/redirects/dto/redirect-scope.ts +++ b/demo/api/src/redirects/dto/redirect-scope.ts @@ -7,6 +7,8 @@ import { IsString } from "class-validator"; @InputType("RedirectScopeInput") // name must not be changed in the app // @TODO: disguise @ObjectType("RedirectScope") and @InputType("RedirectScopeInput") decorators under a custom decorator: f.i. @RedirectScope export class RedirectScope { + [key: string]: string; + @Index() // this does nothing, migration has to be created manually as the entity is in library @Property({ columnType: "text" }) @Field() diff --git a/packages/admin/brevo-admin/src/brevoConfiguration/BrevoConfigPage.tsx b/packages/admin/brevo-admin/src/brevoConfiguration/BrevoConfigPage.tsx index b92550534b0..e169ed5c42e 100644 --- a/packages/admin/brevo-admin/src/brevoConfiguration/BrevoConfigPage.tsx +++ b/packages/admin/brevo-admin/src/brevoConfiguration/BrevoConfigPage.tsx @@ -1,4 +1,4 @@ -import { useContentScope } from "@dextinity/cms-admin"; +import { type ContentScope, useContentScope } from "@dextinity/cms-admin"; import type { JSX } from "react"; import { useBrevoConfig } from "../common/BrevoConfigProvider"; @@ -8,13 +8,10 @@ export function BrevoConfigPage(): JSX.Element { const { scopeParts } = useBrevoConfig(); const { scope: completeScope } = useContentScope(); - const scope = scopeParts.reduce( - (acc, scopePart) => { - acc[scopePart] = completeScope[scopePart]; - return acc; - }, - {} as { [key: string]: unknown }, - ); + const scope = scopeParts.reduce((acc, scopePart) => { + acc[scopePart] = completeScope[scopePart]; + return acc; + }, {} as ContentScope); return ; } diff --git a/packages/admin/brevo-admin/src/brevoContacts/BrevoContactsPage.tsx b/packages/admin/brevo-admin/src/brevoContacts/BrevoContactsPage.tsx index 12f1475abc7..4a4d045defb 100644 --- a/packages/admin/brevo-admin/src/brevoContacts/BrevoContactsPage.tsx +++ b/packages/admin/brevo-admin/src/brevoContacts/BrevoContactsPage.tsx @@ -1,5 +1,5 @@ import { type GridColDef, Stack, StackPage, StackSwitch, StackToolbar } from "@dextinity/admin"; -import { ContentScopeIndicator, useContentScope } from "@dextinity/cms-admin"; +import { type ContentScope, ContentScopeIndicator, useContentScope } from "@dextinity/cms-admin"; import type { DocumentNode } from "graphql"; import type { JSX, ReactNode } from "react"; import { useIntl } from "react-intl"; @@ -27,13 +27,10 @@ function createBrevoContactsPage({ const { scopeParts } = useBrevoConfig(); const { scope: completeScope } = useContentScope(); - const scope = scopeParts.reduce( - (acc, scopePart) => { - acc[scopePart] = completeScope[scopePart]; - return acc; - }, - {} as { [key: string]: unknown }, - ); + const scope = scopeParts.reduce((acc, scopePart) => { + acc[scopePart] = completeScope[scopePart]; + return acc; + }, {} as ContentScope); return ( diff --git a/packages/admin/brevo-admin/src/brevoTestContacts/BrevoTestContactsPage.tsx b/packages/admin/brevo-admin/src/brevoTestContacts/BrevoTestContactsPage.tsx index 3a9f181d741..e800234060b 100644 --- a/packages/admin/brevo-admin/src/brevoTestContacts/BrevoTestContactsPage.tsx +++ b/packages/admin/brevo-admin/src/brevoTestContacts/BrevoTestContactsPage.tsx @@ -1,5 +1,5 @@ import { type GridColDef, Stack, StackPage, StackSwitch, StackToolbar } from "@dextinity/admin"; -import { ContentScopeIndicator, useContentScope } from "@dextinity/cms-admin"; +import { type ContentScope, ContentScopeIndicator, useContentScope } from "@dextinity/cms-admin"; import type { DocumentNode } from "graphql"; import type { JSX, ReactNode } from "react"; import { useIntl } from "react-intl"; @@ -31,13 +31,10 @@ function createBrevoTestContactsPage({ const scopeParts = passedScopeParts ?? brevoConfig.scopeParts; const { scope: completeScope } = useContentScope(); - const scope = scopeParts.reduce( - (acc, scopePart) => { - acc[scopePart] = completeScope[scopePart]; - return acc; - }, - {} as { [key: string]: unknown }, - ); + const scope = scopeParts.reduce((acc, scopePart) => { + acc[scopePart] = completeScope[scopePart]; + return acc; + }, {} as ContentScope); return ( diff --git a/packages/admin/brevo-admin/src/emailCampaigns/EmailCampaignsPage.tsx b/packages/admin/brevo-admin/src/emailCampaigns/EmailCampaignsPage.tsx index 64c16158885..c39b882f6c1 100644 --- a/packages/admin/brevo-admin/src/emailCampaigns/EmailCampaignsPage.tsx +++ b/packages/admin/brevo-admin/src/emailCampaigns/EmailCampaignsPage.tsx @@ -1,5 +1,5 @@ import { Stack, StackPage, StackSwitch, StackToolbar } from "@dextinity/admin"; -import { type BlockInterface, ContentScopeIndicator, useContentScope } from "@dextinity/cms-admin"; +import { type BlockInterface, type ContentScope, ContentScopeIndicator, useContentScope } from "@dextinity/cms-admin"; import type { JSX } from "react"; import { useIntl } from "react-intl"; @@ -20,13 +20,10 @@ export function createEmailCampaignsPage({ EmailCampaignContentBlock }: CreateEm const { scope: completeScope } = useContentScope(); const intl = useIntl(); - const scope = scopeParts.reduce( - (acc, scopePart) => { - acc[scopePart] = completeScope[scopePart]; - return acc; - }, - {} as { [key: string]: unknown }, - ); + const scope = scopeParts.reduce((acc, scopePart) => { + acc[scopePart] = completeScope[scopePart]; + return acc; + }, {} as ContentScope); return ( diff --git a/packages/admin/brevo-admin/src/emailCampaigns/form/TestEmailCampaignForm.tsx b/packages/admin/brevo-admin/src/emailCampaigns/form/TestEmailCampaignForm.tsx index b13b61fb175..af749d8e981 100644 --- a/packages/admin/brevo-admin/src/emailCampaigns/form/TestEmailCampaignForm.tsx +++ b/packages/admin/brevo-admin/src/emailCampaigns/form/TestEmailCampaignForm.tsx @@ -1,7 +1,7 @@ import { gql, useApolloClient, useQuery } from "@apollo/client"; import { Field, FinalForm, FinalFormSelect, SaveButton, Tooltip } from "@dextinity/admin"; import { Info, Newsletter } from "@dextinity/admin-icons"; -import { BlockAdminComponentPaper, BlockAdminComponentSectionGroup, useContentScope } from "@dextinity/cms-admin"; +import { BlockAdminComponentPaper, BlockAdminComponentSectionGroup, type ContentScope, useContentScope } from "@dextinity/cms-admin"; import { Card } from "@mui/material"; import { FormattedMessage } from "react-intl"; @@ -50,13 +50,10 @@ export const TestEmailCampaignForm = ({ id, isSendable = false, isCampaignCreate const { scopeParts } = useBrevoConfig(); const { scope: completeScope } = useContentScope(); - const scope = scopeParts.reduce( - (acc, scopePart) => { - acc[scopePart] = completeScope[scopePart]; - return acc; - }, - {} as { [key: string]: unknown }, - ); + const scope = scopeParts.reduce((acc, scopePart) => { + acc[scopePart] = completeScope[scopePart]; + return acc; + }, {} as ContentScope); // Contact creation is limited to 100 at a time. Therefore, 100 contacts are queried without using pagination. const { data, loading, error } = useQuery(brevoTestContactsSelectQuery, { diff --git a/packages/admin/brevo-admin/src/targetGroups/TargetGroupsPage.tsx b/packages/admin/brevo-admin/src/targetGroups/TargetGroupsPage.tsx index e56f7083a2c..bc0b2131c53 100644 --- a/packages/admin/brevo-admin/src/targetGroups/TargetGroupsPage.tsx +++ b/packages/admin/brevo-admin/src/targetGroups/TargetGroupsPage.tsx @@ -1,5 +1,5 @@ import { Stack, StackPage, StackSwitch, Toolbar } from "@dextinity/admin"; -import { ContentScopeIndicator, useContentScope } from "@dextinity/cms-admin"; +import { type ContentScope, ContentScopeIndicator, useContentScope } from "@dextinity/cms-admin"; import type { DocumentNode } from "graphql"; import type { JSX, ReactNode } from "react"; import { useIntl } from "react-intl"; @@ -26,13 +26,10 @@ export function createTargetGroupsPage({ additionalFormFields, nodeFragment, inp const { scope: completeScope } = useContentScope(); const intl = useIntl(); - const scope = scopeParts.reduce( - (acc, scopePart) => { - acc[scopePart] = completeScope[scopePart]; - return acc; - }, - {} as { [key: string]: unknown }, - ); + const scope = scopeParts.reduce((acc, scopePart) => { + acc[scopePart] = completeScope[scopePart]; + return acc; + }, {} as ContentScope); return ( diff --git a/packages/admin/cms-admin/src/contentScope/ContentScopeIndicator.tsx b/packages/admin/cms-admin/src/contentScope/ContentScopeIndicator.tsx index 5f0c9a0d9ab..d89392c69d0 100644 --- a/packages/admin/cms-admin/src/contentScope/ContentScopeIndicator.tsx +++ b/packages/admin/cms-admin/src/contentScope/ContentScopeIndicator.tsx @@ -25,7 +25,7 @@ export const ContentScopeIndicator = ({ global = false, scope: passedScope, chil const label = values.find((value) => { return value.scope[scopePart] === scope[scopePart]; })?.label; - return (label && label[scopePart]) ?? (scope[scopePart] ? capitalizeString(scope[scopePart]) : undefined); + return (label && label[scopePart]) ?? (scope[scopePart] ? capitalizeString(String(scope[scopePart])) : undefined); }; let content: ReactNode; diff --git a/packages/admin/cms-admin/src/contentScope/ContentScopeSelect.tsx b/packages/admin/cms-admin/src/contentScope/ContentScopeSelect.tsx index 1f85bd0dac7..e8600d45549 100644 --- a/packages/admin/cms-admin/src/contentScope/ContentScopeSelect.tsx +++ b/packages/admin/cms-admin/src/contentScope/ContentScopeSelect.tsx @@ -60,8 +60,11 @@ export function ContentScopeSelect({ if (searchable) { filteredOptions = options.filter((option) => { return ( - Object.values(option.scope).some((value) => value.toLowerCase().includes(searchValue.toLowerCase())) || - Object.values(option.label || []).some((value) => value?.toLowerCase().includes(searchValue.toLowerCase())) + Object.values(option.scope).some((value) => + String(value ?? "") + .toLowerCase() + .includes(searchValue.toLowerCase()), + ) || Object.values(option.label || []).some((value) => value?.toLowerCase().includes(searchValue.toLowerCase())) ); }); } @@ -71,12 +74,13 @@ export function ContentScopeSelect({ if (groupBy) { if (hasMultipleDimensions) { for (const option of filteredOptions) { - const groupForOption = groups.find((group) => group.value === option.scope[groupBy]); + const groupValue = String(option.scope[groupBy] ?? ""); + const groupForOption = groups.find((group) => group.value === groupValue); if (groupForOption) { groupForOption.options.push(option); } else { - groups.push({ value: option.scope[groupBy], label: option.label ? option.label[groupBy] : undefined, options: [option] }); + groups.push({ value: groupValue, label: option.label ? option.label[groupBy] : undefined, options: [option] }); } } } else { @@ -125,7 +129,7 @@ export function ContentScopeSelect({ if (!renderSelectedOption) { renderSelectedOption = (option) => { return Object.keys(option.scope) - .map((key) => humanReadableLabel({ label: option.label ? option.label[key] : undefined, value: option.scope[key] })) + .map((key) => humanReadableLabel({ label: option.label ? option.label[key] : undefined, value: String(option.scope[key] ?? "") })) .join(" / "); }; } diff --git a/packages/admin/cms-admin/src/contentScope/Provider.tsx b/packages/admin/cms-admin/src/contentScope/Provider.tsx index 16c16b92a54..1161f5324c7 100644 --- a/packages/admin/cms-admin/src/contentScope/Provider.tsx +++ b/packages/admin/cms-admin/src/contentScope/Provider.tsx @@ -8,8 +8,7 @@ import { NoContentScopeFallback } from "./noContentScopeFallback/NoContentScopeF import { defaultCreatePath } from "./utils/defaultCreatePath"; export interface ContentScope { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; + [key: string]: string | number | null | undefined; } type ContentScopeLocation = { @@ -36,10 +35,7 @@ const defaultContentScopeContext: ContentScopeContext = { location: defaultContentScopeLocation, }; -type NonNull = T extends null ? never : T; -type NonNullRecord = { - [P in keyof T]: NonNull; -}; +type ContentScopeRouterParams = Record; type SetContentScopeAction = (state: ContentScope) => ContentScope; @@ -64,7 +60,7 @@ const Context = createContext(defaultContentScopeContext); const NullValueAsString = "-"; // used to represent null-values in the url -function parseScopeFromRouterMatchParams(params: NonNullRecord): ContentScope { +function parseScopeFromRouterMatchParams(params: ContentScopeRouterParams): ContentScope { return Object.entries(params).reduce((a, [key, value]) => { return { ...a, @@ -73,13 +69,13 @@ function parseScopeFromRouterMatchParams(params: NonNullRecord): C }, {} as ContentScope); } -function formatScopeToRouterMatchParams(scope: Partial): NonNullRecord { +function formatScopeToRouterMatchParams(scope: ContentScope): ContentScopeRouterParams { return Object.entries(scope).reduce((a, [key, value]) => { return { ...a, - [key]: !value || value === null ? NullValueAsString : value, + [key]: value === null || value === undefined || value === "" ? NullValueAsString : String(value), }; - }, {} as NonNullRecord); + }, {} as ContentScopeRouterParams); } function defaultCreateUrl(scope: ContentScope) { @@ -93,7 +89,7 @@ function defaultCreateUrl(scope: ContentScope) { export function useContentScope(): UseContentScopeApi { const context = useContext(Context); const history = useHistory(); - const matchContextScope = useRouteMatch>(context?.path || ""); + const matchContextScope = useRouteMatch(context?.path || ""); const matchDefault = useRouteMatch(); const match = matchContextScope || matchDefault; @@ -124,7 +120,7 @@ export function useContentScope(): UseContentScopeApi { export interface ContentScopeProviderProps { defaultValue?: ContentScope; values?: ContentScopeValues; - children: (p: { match: match> }) => ReactNode; + children: (p: { match: match }) => ReactNode; location?: ContentScopeLocation; /** @@ -151,7 +147,7 @@ export function ContentScopeProvider({ } const path = location.createPath(values); - const match = useRouteMatch>(path); + const match = useRouteMatch(path); const [redirectPathAfterChange, setRedirectPathAfterChange] = useState(""); if (values.length === 0) { diff --git a/packages/admin/cms-admin/src/contentScope/__stories__/ContentScopeSelect.stories.tsx b/packages/admin/cms-admin/src/contentScope/__stories__/ContentScopeSelect.stories.tsx index 700a99611e9..654bb579bae 100644 --- a/packages/admin/cms-admin/src/contentScope/__stories__/ContentScopeSelect.stories.tsx +++ b/packages/admin/cms-admin/src/contentScope/__stories__/ContentScopeSelect.stories.tsx @@ -297,9 +297,9 @@ export const GroupingWithOptionalScopeParts = { renderOption={(option, query, isSelected) => { let text: string; if (option.scope.company === undefined) { - text = option.label?.country ?? option.scope.country; + text = option.label?.country ?? String(option.scope.country); } else { - text = option.label?.company ?? option.scope.company; + text = option.label?.company ?? String(option.scope.company); } const matches = findTextMatches(text, query); diff --git a/packages/admin/cms-admin/src/dam/config/DamScopeContext.ts b/packages/admin/cms-admin/src/dam/config/DamScopeContext.ts index d762e6e9f8e..f6e51cc3935 100644 --- a/packages/admin/cms-admin/src/dam/config/DamScopeContext.ts +++ b/packages/admin/cms-admin/src/dam/config/DamScopeContext.ts @@ -1,3 +1,5 @@ import { createContext } from "react"; -export const DamScopeContext = createContext>({}); +import type { ContentScope } from "../../contentScope/Provider"; + +export const DamScopeContext = createContext({}); diff --git a/packages/admin/cms-admin/src/dam/config/DamScopeProvider.tsx b/packages/admin/cms-admin/src/dam/config/DamScopeProvider.tsx index 9b52683be0e..defb67bc7c7 100644 --- a/packages/admin/cms-admin/src/dam/config/DamScopeProvider.tsx +++ b/packages/admin/cms-admin/src/dam/config/DamScopeProvider.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from "react"; -import { useContentScope } from "../../contentScope/Provider"; +import { type ContentScope, useContentScope } from "../../contentScope/Provider"; import { useDamConfig } from "./damConfig"; import { DamScopeContext } from "./DamScopeContext"; @@ -8,15 +8,12 @@ export function DamScopeProvider({ children }: { children?: ReactNode }) { const { scopeParts = [] } = useDamConfig(); const { scope: completeScope } = useContentScope(); - const damScope = scopeParts.reduce( - (damScope, scope) => { - if (completeScope[scope] !== undefined) { - damScope[scope] = completeScope[scope]; - } - return damScope; - }, - {} as Record, - ); + const damScope = scopeParts.reduce((damScope, scope) => { + if (completeScope[scope] !== undefined) { + damScope[scope] = completeScope[scope]; + } + return damScope; + }, {} as ContentScope); return {children}; } diff --git a/packages/admin/cms-admin/src/dam/config/useDamScope.ts b/packages/admin/cms-admin/src/dam/config/useDamScope.ts index 6f6ead7d0c4..82da7eb8a4a 100644 --- a/packages/admin/cms-admin/src/dam/config/useDamScope.ts +++ b/packages/admin/cms-admin/src/dam/config/useDamScope.ts @@ -1,8 +1,9 @@ import { useContext } from "react"; +import type { ContentScope } from "../../contentScope/Provider"; import { DamScopeContext } from "./DamScopeContext"; -function useDamScope(): Record { +function useDamScope(): ContentScope { return useContext(DamScopeContext); } diff --git a/packages/admin/cms-admin/src/form/file/upload.ts b/packages/admin/cms-admin/src/form/file/upload.ts index 7663693fc2f..6f4e423f86a 100644 --- a/packages/admin/cms-admin/src/form/file/upload.ts +++ b/packages/admin/cms-admin/src/form/file/upload.ts @@ -1,9 +1,10 @@ +import type { ContentScope } from "../../contentScope/Provider"; import type { GQLUpdateDamFileInput } from "../../graphql.generated"; interface UploadFileData { file: File & Pick & { importSource?: { importSourceType: string; importSourceId: string } }; - scope: Record; + scope: ContentScope; folderId?: string; /** * @deprecated Set `file.importSource.importSourceId` instead diff --git a/packages/admin/cms-admin/src/pages/pageTree/findAvailableSlug.ts b/packages/admin/cms-admin/src/pages/pageTree/findAvailableSlug.ts index d00c0f66f9b..f1942e88f76 100644 --- a/packages/admin/cms-admin/src/pages/pageTree/findAvailableSlug.ts +++ b/packages/admin/cms-admin/src/pages/pageTree/findAvailableSlug.ts @@ -1,6 +1,8 @@ import { type ApolloClient, gql } from "@apollo/client"; import { LocalErrorScopeApolloContext } from "@dextinity/admin"; +import type { ContentScope } from "../../contentScope/Provider"; + const slugAvailableQuery = gql` query FindAvailableSlug($parentId: ID, $slug: String!, $scope: PageTreeNodeScopeInput!) { pageTreeNodeSlugAvailable(parentId: $parentId, slug: $slug, scope: $scope) @@ -9,7 +11,7 @@ const slugAvailableQuery = gql` export async function findAvailableSlug( apolloClient: ApolloClient, - { slug, name, parentId, scope }: { slug: string; name: string; parentId: string | null; scope: Record }, + { slug, name, parentId, scope }: { slug: string; name: string; parentId: string | null; scope: ContentScope }, ): Promise<{ slug: string; name: string }> { let candidateSlug = slug; let candidateName = name; diff --git a/packages/admin/cms-admin/src/pages/pageTree/useCopyPastePages/createInboxFolder.ts b/packages/admin/cms-admin/src/pages/pageTree/useCopyPastePages/createInboxFolder.ts index 0750a747f5b..02e74606fec 100644 --- a/packages/admin/cms-admin/src/pages/pageTree/useCopyPastePages/createInboxFolder.ts +++ b/packages/admin/cms-admin/src/pages/pageTree/useCopyPastePages/createInboxFolder.ts @@ -2,6 +2,7 @@ import { type ApolloClient, gql } from "@apollo/client"; import { LocalErrorScopeApolloContext } from "@dextinity/admin"; import { format } from "date-fns"; +import type { ContentScope } from "../../../contentScope/Provider"; import type { GQLCreateInboxFolderMutation, GQLCreateInboxFolderMutationVariables } from "./createInboxFolder.generated"; export const createInboxFolder = async ({ @@ -10,8 +11,8 @@ export const createInboxFolder = async ({ sourceScopes, }: { client: ApolloClient; - targetScope: Record; - sourceScopes: Record[]; + targetScope: ContentScope; + sourceScopes: ContentScope[]; }) => { const scopeString = sourceScopes.length === 0 ? "unknown" : sourceScopes.map((scope) => Object.values(scope).join("-")).join(", "); const date = new Date(); diff --git a/packages/admin/cms-admin/src/pages/pageTree/useCopyPastePages/sendPages.tsx b/packages/admin/cms-admin/src/pages/pageTree/useCopyPastePages/sendPages.tsx index ba06743854c..272ae743881 100644 --- a/packages/admin/cms-admin/src/pages/pageTree/useCopyPastePages/sendPages.tsx +++ b/packages/admin/cms-admin/src/pages/pageTree/useCopyPastePages/sendPages.tsx @@ -61,7 +61,7 @@ interface SendPagesDependencies { scope: ContentScope; documentTypes: PageTreeConfig["documentTypes"]; apiUrl: string; - damScope: Record; + damScope: ContentScope; currentCategory: string; damBasePath: string; } @@ -96,7 +96,7 @@ export async function sendPages( updateProgress(0, ); { let progressPages = 0; - const sourceScopes: Record[] = []; + const sourceScopes: ContentScope[] = []; for (const sourcePage of pages) { const documentType = documentTypes[sourcePage.documentType]; if (!documentType) { @@ -341,7 +341,7 @@ function unhandledDependenciesFromDocument( existingReplacements, hasDamScope = false, targetDamScope, - }: { existingReplacements: ReplaceDependencyObject[]; hasDamScope?: boolean; targetDamScope: Record }, + }: { existingReplacements: ReplaceDependencyObject[]; hasDamScope?: boolean; targetDamScope: ContentScope }, ) { const unhandledDependencies = documentType.dependencies(document).filter((dependency) => { if (isDamFileDependency(dependency)) { @@ -391,7 +391,7 @@ function fileDependenciesFromDocument(documentType: DocumentInterface, document: function isDamFileDependency( dependency: BlockDependency, -): dependency is BlockDependency & { data: { damFile: GQLDamFile & { scope?: Record } } } { +): dependency is BlockDependency & { data: { damFile: GQLDamFile & { scope?: ContentScope } } } { // eslint-disable-next-line @typescript-eslint/no-explicit-any return dependency.targetGraphqlObjectType === "DamFile" && dependency.data && (dependency.data as any).damFile; } diff --git a/packages/admin/cms-admin/src/pages/pageTree/useTranslatePagesAction.tsx b/packages/admin/cms-admin/src/pages/pageTree/useTranslatePagesAction.tsx index c4562592a95..61bf35fc0ef 100644 --- a/packages/admin/cms-admin/src/pages/pageTree/useTranslatePagesAction.tsx +++ b/packages/admin/cms-admin/src/pages/pageTree/useTranslatePagesAction.tsx @@ -108,7 +108,7 @@ export function useTranslatePagesAction({ pages, documentTypes }: Props): { const [translatedName, ...rest] = translatedTexts; translatedContentTexts = rest; - const translatedSlug = transformToSlug(translatedName, scope.language); + const translatedSlug = transformToSlug(translatedName, String(scope.language)); if (translatedName !== page.name || translatedSlug !== page.slug) { const available = await findAvailableSlug(apolloClient, { diff --git a/packages/admin/cms-admin/src/redirects/RedirectForm.tsx b/packages/admin/cms-admin/src/redirects/RedirectForm.tsx index b8cb7bc5762..0e898c2b4f5 100644 --- a/packages/admin/cms-admin/src/redirects/RedirectForm.tsx +++ b/packages/admin/cms-admin/src/redirects/RedirectForm.tsx @@ -24,6 +24,7 @@ import { FormattedMessage, useIntl } from "react-intl"; import { createFinalFormBlock } from "../blocks/form/createFinalFormBlock"; import type { BlockInterface, BlockState } from "../blocks/types"; import { ContentScopeIndicator } from "../contentScope/ContentScopeIndicator"; +import type { ContentScope } from "../contentScope/Provider"; import type { GQLRedirectSourceType } from "../graphql.generated"; import type { GQLRedirectSourceAvailableQuery, GQLRedirectSourceAvailableQueryVariables } from "./RedirectForm.generated"; import { redirectDetailQuery } from "./RedirectForm.gql"; @@ -42,7 +43,7 @@ interface Props { id?: string; mode: "edit" | "add"; linkBlock: BlockInterface; - scope: Record; + scope: ContentScope; } export interface FormValues { diff --git a/packages/admin/cms-admin/src/redirects/RedirectsGrid.tsx b/packages/admin/cms-admin/src/redirects/RedirectsGrid.tsx index f699464e5e9..cb1557fc4f9 100644 --- a/packages/admin/cms-admin/src/redirects/RedirectsGrid.tsx +++ b/packages/admin/cms-admin/src/redirects/RedirectsGrid.tsx @@ -35,6 +35,7 @@ import { FormattedMessage, useIntl } from "react-intl"; import { BlockPreviewContent } from "../blocks/common/blockRow/BlockPreviewContent"; import type { BlockInterface } from "../blocks/types"; +import type { ContentScope } from "../contentScope/Provider"; import { DataGrid } from "../dataGrid/DataGrid"; import RedirectActiveness from "./RedirectActiveness"; import { deleteRedirectMutation, deleteRedirectsMutation, paginatedRedirectsQuery } from "./RedirectsGrid.gql"; @@ -48,7 +49,7 @@ import { interface Props { linkBlock: BlockInterface; - scope: Record; + scope: ContentScope; } interface RedirectsGridToolbarProps extends GridToolbarProps { diff --git a/packages/admin/cms-admin/src/redirects/redirectsConfig.ts b/packages/admin/cms-admin/src/redirects/redirectsConfig.ts index 67673d18c0f..582b5497439 100644 --- a/packages/admin/cms-admin/src/redirects/redirectsConfig.ts +++ b/packages/admin/cms-admin/src/redirects/redirectsConfig.ts @@ -1,5 +1,5 @@ import { useDextinityConfig } from "../config/DextinityConfigContext"; -import { useContentScope } from "../contentScope/Provider"; +import { type ContentScope, useContentScope } from "../contentScope/Provider"; export interface RedirectsConfig { scopeParts?: string[]; @@ -15,18 +15,15 @@ function useRedirectsConfig(): RedirectsConfig { return dextinityConfig.redirects; } -export function useRedirectsScope(): { [key: string]: unknown } { +export function useRedirectsScope(): ContentScope { const { scopeParts } = useRedirectsConfig(); const { scope: completeScope } = useContentScope(); const redirectScope = scopeParts?.length - ? scopeParts.reduce( - (acc, scopePart) => { - acc[scopePart] = completeScope[scopePart]; - return acc; - }, - {} as { [key: string]: unknown }, - ) + ? scopeParts.reduce((acc, scopePart) => { + acc[scopePart] = completeScope[scopePart]; + return acc; + }, {} as ContentScope) : completeScope; return redirectScope; diff --git a/packages/admin/cms-admin/src/redirects/submitMutation.ts b/packages/admin/cms-admin/src/redirects/submitMutation.ts index 4c30a92121b..18017656bb2 100644 --- a/packages/admin/cms-admin/src/redirects/submitMutation.ts +++ b/packages/admin/cms-admin/src/redirects/submitMutation.ts @@ -3,6 +3,7 @@ import type { ApolloError } from "@apollo/client/errors"; import type { FetchResult } from "@apollo/client/link/core"; import type { BlockInterface } from "../blocks/types"; +import type { ContentScope } from "../contentScope/Provider"; import type { GQLRedirectInput } from "../graphql.generated"; import { createRedirectMutation, @@ -28,7 +29,7 @@ export const useSubmitMutation = ( mode: "edit" | "add", id: string | undefined, linkBlock: BlockInterface, - scope: Record, + scope: ContentScope, ): [ (values: FormValues) => Promise>, { loading: boolean; error: ApolloError | undefined }, diff --git a/packages/admin/cms-admin/src/translation/AzureAiTranslatorProvider.tsx b/packages/admin/cms-admin/src/translation/AzureAiTranslatorProvider.tsx index e663cac560a..ca4e7fca43e 100644 --- a/packages/admin/cms-admin/src/translation/AzureAiTranslatorProvider.tsx +++ b/packages/admin/cms-admin/src/translation/AzureAiTranslatorProvider.tsx @@ -23,7 +23,7 @@ export const AzureAiTranslatorProvider = ({ children, enabled = false, ...rest } const { data } = await apolloClient.query({ query: translationQuery, variables: { - input: { text, targetLanguage: scope.language }, + input: { text, targetLanguage: String(scope.language) }, }, }); return data.azureAiTranslate; @@ -32,7 +32,7 @@ export const AzureAiTranslatorProvider = ({ children, enabled = false, ...rest } const { data } = await apolloClient.query<{ azureAiTranslateBatch: string[] }>({ query: batchTranslationQuery, variables: { - input: { texts, targetLanguage: scope.language }, + input: { texts, targetLanguage: String(scope.language) }, }, fetchPolicy: "no-cache", }); diff --git a/packages/admin/cms-admin/src/warnings/WarningsGrid.tsx b/packages/admin/cms-admin/src/warnings/WarningsGrid.tsx index 26a718d5615..4db24457faf 100644 --- a/packages/admin/cms-admin/src/warnings/WarningsGrid.tsx +++ b/packages/admin/cms-admin/src/warnings/WarningsGrid.tsx @@ -94,7 +94,7 @@ export function WarningsGrid() { if (item.label && item.label[key]) { label.push(item.label[key]); } else if (value) { - label.push(capitalCase(value)); + label.push(capitalCase(String(value))); } } diff --git a/packages/api/brevo-api/generate-schema.ts b/packages/api/brevo-api/generate-schema.ts index 83807505bb4..5f06cd70616 100644 --- a/packages/api/brevo-api/generate-schema.ts +++ b/packages/api/brevo-api/generate-schema.ts @@ -26,7 +26,7 @@ import { BrevoPermission } from "./src"; @ObjectType("EmailCampaignContentScope") @InputType("EmailCampaignContentScopeInput") class EmailCampaignScope implements EmailCampaignScopeInterface { - [key: string]: unknown; + [key: string]: string | number | null | undefined; // empty scope @Field({ nullable: true }) thisScopeHasNoFields____?: string; // just anything so this class has at least one field and can be interpreted as a gql-object/input type diff --git a/packages/api/brevo-api/src/blacklisted-contacts/entity/blacklisted-contacts.entity.factory.ts b/packages/api/brevo-api/src/blacklisted-contacts/entity/blacklisted-contacts.entity.factory.ts index 5aa08d57c89..114bb3fc9c6 100644 --- a/packages/api/brevo-api/src/blacklisted-contacts/entity/blacklisted-contacts.entity.factory.ts +++ b/packages/api/brevo-api/src/blacklisted-contacts/entity/blacklisted-contacts.entity.factory.ts @@ -44,7 +44,7 @@ export function createBlacklistedContactsEntity({ Scope }: { Scope: Type Scope) @Field(() => Scope) - scope: typeof Scope; + scope: EmailCampaignScopeInterface; } return BrevoBlacklistedContacts; diff --git a/packages/api/brevo-api/src/brevo-config/brevo-config.resolver.ts b/packages/api/brevo-api/src/brevo-config/brevo-config.resolver.ts index 090953935d3..16ab6399741 100644 --- a/packages/api/brevo-api/src/brevo-config/brevo-config.resolver.ts +++ b/packages/api/brevo-api/src/brevo-config/brevo-config.resolver.ts @@ -73,7 +73,7 @@ export function createBrevoConfigResolver({ @Query(() => [BrevoApiSender], { nullable: true }) async brevoSenders( @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) - scope: typeof Scope, + scope: EmailCampaignScopeInterface, ): Promise | undefined> { const senders = await this.brevoSenderApiService.getSenders(scope); return senders; @@ -83,7 +83,7 @@ export function createBrevoConfigResolver({ @Query(() => [BrevoApiEmailTemplate], { nullable: true }) async brevoDoubleOptInTemplates( @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) - scope: typeof Scope, + scope: EmailCampaignScopeInterface, ): Promise | undefined> { const { templates } = await this.brevoTransactionalEmailsApiService.getEmailTemplates(scope); const doubleOptInTemplates = templates?.filter((template) => template.tag === "optin" && template.isActive); @@ -94,7 +94,7 @@ export function createBrevoConfigResolver({ @RequiredPermission("brevoNewsletter") async isBrevoConfigDefined( @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) - scope: typeof Scope, + scope: EmailCampaignScopeInterface, ): Promise { const brevoConfig = await this.repository.findOne({ scope }); return !!brevoConfig; @@ -103,7 +103,7 @@ export function createBrevoConfigResolver({ @Query(() => BrevoConfig, { nullable: true }) async brevoConfig( @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) - scope: typeof Scope, + scope: EmailCampaignScopeInterface, ): Promise { const brevoConfig = await this.repository.findOne({ scope }); return brevoConfig; @@ -112,7 +112,7 @@ export function createBrevoConfigResolver({ @Mutation(() => BrevoConfig) async createBrevoConfig( @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) - scope: typeof Scope, + scope: EmailCampaignScopeInterface, @Args("input", { type: () => BrevoConfigInput }) input: BrevoConfigInput, ): Promise { if (!(await this.brevoIsValidSender({ email: input.senderMail, name: input.senderName, scope }))) { diff --git a/packages/api/brevo-api/src/brevo-config/entities/brevo-config-entity.factory.ts b/packages/api/brevo-api/src/brevo-config/entities/brevo-config-entity.factory.ts index 28207a2c93f..a2fd4eac583 100644 --- a/packages/api/brevo-api/src/brevo-config/entities/brevo-config-entity.factory.ts +++ b/packages/api/brevo-api/src/brevo-config/entities/brevo-config-entity.factory.ts @@ -72,7 +72,7 @@ export class BrevoConfigEntityFactory { @Embedded(() => Scope) @Field(() => Scope) - scope: typeof Scope; + scope: EmailCampaignScopeInterface; } return BrevoConfig; diff --git a/packages/api/brevo-api/src/brevo-contact/brevo-contact-import.console.ts b/packages/api/brevo-api/src/brevo-contact/brevo-contact-import.console.ts index b836b589f5e..a3018e2ab9d 100644 --- a/packages/api/brevo-api/src/brevo-contact/brevo-contact-import.console.ts +++ b/packages/api/brevo-api/src/brevo-contact/brevo-contact-import.console.ts @@ -15,7 +15,7 @@ import { EmailCampaignScopeInterface } from "../types"; interface CommandOptions { path: string; - scope: Type; + scope: EmailCampaignScopeInterface; targetGroupIds: string[]; sendDoubleOptIn: boolean; } @@ -59,8 +59,8 @@ export function createBrevoContactImportConsole({ Scope }: { Scope: Type { - const parsedScope = JSON.parse(scope) as typeof Scope; + parseScope(scope: string): EmailCampaignScopeInterface { + const parsedScope = JSON.parse(scope) as EmailCampaignScopeInterface; const validateErrors = validateSync(parsedScope); if (validateErrors.length) { @@ -111,7 +111,7 @@ export function createBrevoContactImportConsole({ Scope }: { Scope: Type): Promise { + async validateRedirectUrl(urlToValidate: string, scope: EmailCampaignScopeInterface): Promise { const configForScope = await this.brevoConfigRepository.findOneOrFail({ scope }); if (!configForScope) { diff --git a/packages/api/brevo-api/src/brevo-contact/brevo-contact.console.ts b/packages/api/brevo-api/src/brevo-contact/brevo-contact.console.ts index eb271da4e5f..9f029e6caf4 100644 --- a/packages/api/brevo-api/src/brevo-contact/brevo-contact.console.ts +++ b/packages/api/brevo-api/src/brevo-contact/brevo-contact.console.ts @@ -30,14 +30,12 @@ export class DeleteUnsubscribedBrevoContactsConsole extends CommandRunner { let offset = 0; do { - const contacts = await this.brevoApiContactsService.findContacts(limit, offset, { - scope: targetGroup.scope, - }); + const contacts = await this.brevoApiContactsService.findContacts(limit, offset, targetGroup.scope); const blacklistedContacts = contacts.filter((contact) => contact.emailBlacklisted === true); if (blacklistedContacts.length > 0) { - await this.brevoApiContactsService.deleteContacts(blacklistedContacts, { scope: targetGroup.scope }); + await this.brevoApiContactsService.deleteContacts(blacklistedContacts, targetGroup.scope); } hasMoreContacts = !(contacts.length < limit); diff --git a/packages/api/brevo-api/src/brevo-contact/brevo-contact.resolver.ts b/packages/api/brevo-api/src/brevo-contact/brevo-contact.resolver.ts index 646cd584747..0978aff97f0 100644 --- a/packages/api/brevo-api/src/brevo-contact/brevo-contact.resolver.ts +++ b/packages/api/brevo-api/src/brevo-contact/brevo-contact.resolver.ts @@ -61,7 +61,7 @@ export function createBrevoContactResolver({ @AffectedEntity(BrevoContact) async brevoContact( @Args("id", { type: () => Int }) id: number, - @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: typeof Scope, + @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: EmailCampaignScopeInterface, ): Promise { const brevoContact = await this.brevoContactsApiService.findContact(id, scope); @@ -158,7 +158,7 @@ export function createBrevoContactResolver({ @AffectedEntity(BrevoContact) async updateBrevoContact( @Args("id", { type: () => Int }) id: number, - @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: typeof Scope, + @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: EmailCampaignScopeInterface, @Args("input", { type: () => BrevoContactUpdateInput }) input: BrevoContactUpdateInputInterface, ): Promise { // update attributes of contact before (un)assigning to target groups because they cannot be correctly validated for completeness @@ -206,7 +206,7 @@ export function createBrevoContactResolver({ @Mutation(() => SubscribeResponse) @RequiredPermission(["brevoNewsletter"], { skipScopeCheck: true }) async createBrevoContact( - @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: typeof Scope, + @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: EmailCampaignScopeInterface, @Args("input", { type: () => BrevoContactInput }) input: BrevoContactInputInterface, @GetCurrentUser() user: CurrentUser, @@ -232,7 +232,7 @@ export function createBrevoContactResolver({ @Mutation(() => SubscribeResponse) @RequiredPermission(["brevoNewsletter"], { skipScopeCheck: true }) async createBrevoTestContact( - @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: typeof Scope, + @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: EmailCampaignScopeInterface, @Args("input", { type: () => BrevoTestContactInput }) input: BrevoContactInputInterface, ): Promise { @@ -278,7 +278,7 @@ export function createBrevoContactResolver({ @AffectedEntity(BrevoContact) async deleteBrevoContact( @Args("id", { type: () => Int }) id: number, - @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: typeof Scope, + @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: EmailCampaignScopeInterface, ): Promise { const contact = await this.brevoContactsApiService.findContact(id, scope); if (!contact) { @@ -312,7 +312,7 @@ export function createBrevoContactResolver({ @AffectedEntity(BrevoContact) async deleteBrevoTestContact( @Args("id", { type: () => Int }) id: number, - @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: typeof Scope, + @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) scope: EmailCampaignScopeInterface, ): Promise { const contact = await this.brevoContactsApiService.findContact(id, scope); if (!contact) { @@ -347,7 +347,7 @@ export function createBrevoContactResolver({ async subscribeBrevoContact( @Args("input", { type: () => BrevoContactSubscribeInput }) data: SubscribeInputInterface, @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) - scope: typeof Scope, + scope: EmailCampaignScopeInterface, ): Promise { return this.brevoContactsService.subscribeBrevoContact(data, scope); } diff --git a/packages/api/brevo-api/src/brevo-contact/validator/redirect-url.validator.ts b/packages/api/brevo-api/src/brevo-contact/validator/redirect-url.validator.ts index 0c82cf22442..b29c8620a89 100644 --- a/packages/api/brevo-api/src/brevo-contact/validator/redirect-url.validator.ts +++ b/packages/api/brevo-api/src/brevo-contact/validator/redirect-url.validator.ts @@ -1,6 +1,6 @@ import { InjectRepository } from "@mikro-orm/nestjs"; import { EntityRepository } from "@mikro-orm/postgresql"; -import { Inject, Injectable } from "@nestjs/common"; +import { Inject, Injectable, Type } from "@nestjs/common"; import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator"; import { BrevoConfigInterface } from "src/brevo-config/entities/brevo-config-entity.factory"; import { EmailCampaignScopeInterface } from "src/types"; @@ -8,7 +8,7 @@ import { EmailCampaignScopeInterface } from "src/types"; import { BrevoModuleConfig } from "../../config/brevo-module.config"; import { BREVO_MODULE_CONFIG } from "../../config/brevo-module.constants"; -export const IsValidRedirectURL = (scope: EmailCampaignScopeInterface, validationOptions?: ValidationOptions) => { +export const IsValidRedirectURL = (scope: Type, validationOptions?: ValidationOptions) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any return (object: Record, propertyName: string): void => { registerDecorator({ diff --git a/packages/api/brevo-api/src/brevo-email-import-log/entity/brevo-email-import-log.entity.factory.ts b/packages/api/brevo-api/src/brevo-email-import-log/entity/brevo-email-import-log.entity.factory.ts index 11670bdd29a..95bcf4c7446 100644 --- a/packages/api/brevo-api/src/brevo-email-import-log/entity/brevo-email-import-log.entity.factory.ts +++ b/packages/api/brevo-api/src/brevo-email-import-log/entity/brevo-email-import-log.entity.factory.ts @@ -56,7 +56,7 @@ export function createBrevoEmailImportLogEntity({ Scope }: { Scope: Type Scope) @Field(() => Scope) - scope: typeof Scope; + scope: EmailCampaignScopeInterface; @Property({ columnType: "uuid" }) @IsUndefinable() diff --git a/packages/api/brevo-api/src/email-campaign/email-campaign.resolver.ts b/packages/api/brevo-api/src/email-campaign/email-campaign.resolver.ts index bab188ffde1..ed239fde430 100644 --- a/packages/api/brevo-api/src/email-campaign/email-campaign.resolver.ts +++ b/packages/api/brevo-api/src/email-campaign/email-campaign.resolver.ts @@ -91,7 +91,7 @@ export function createEmailCampaignsResolver({ @Mutation(() => BrevoEmailCampaign) async createBrevoEmailCampaign( @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) - scope: typeof Scope, + scope: EmailCampaignScopeInterface, @Args("input", { type: () => EmailCampaignInput }, new DynamicDtoValidationPipe(EmailCampaignInput)) input: EmailCampaignInputInterface, ): Promise { const campaign = this.repository.create({ diff --git a/packages/api/brevo-api/src/email-campaign/entities/email-campaign-entity.factory.ts b/packages/api/brevo-api/src/email-campaign/entities/email-campaign-entity.factory.ts index 11343ca60b5..f27efda58cd 100644 --- a/packages/api/brevo-api/src/email-campaign/entities/email-campaign-entity.factory.ts +++ b/packages/api/brevo-api/src/email-campaign/entities/email-campaign-entity.factory.ts @@ -87,7 +87,7 @@ export function createEmailCampaignEntity({ @Embedded(() => Scope) @Field(() => Scope) - scope: typeof Scope; + scope: EmailCampaignScopeInterface; } return BrevoEmailCampaign; diff --git a/packages/api/brevo-api/src/target-group/entity/target-group-entity.factory.ts b/packages/api/brevo-api/src/target-group/entity/target-group-entity.factory.ts index d4caecd955c..c603e7b6cc5 100644 --- a/packages/api/brevo-api/src/target-group/entity/target-group-entity.factory.ts +++ b/packages/api/brevo-api/src/target-group/entity/target-group-entity.factory.ts @@ -71,7 +71,7 @@ export function createTargetGroupEntity({ @Embedded(() => Scope) @Field(() => Scope) - scope: typeof Scope; + scope: EmailCampaignScopeInterface; @Property({ columnType: "int", nullable: true }) @Field(() => Int, { nullable: true }) diff --git a/packages/api/brevo-api/src/target-group/target-group.resolver.ts b/packages/api/brevo-api/src/target-group/target-group.resolver.ts index 1d81147567a..5fe1d718993 100644 --- a/packages/api/brevo-api/src/target-group/target-group.resolver.ts +++ b/packages/api/brevo-api/src/target-group/target-group.resolver.ts @@ -85,7 +85,7 @@ export function createTargetGroupsResolver({ @Mutation(() => BrevoTargetGroup) async createBrevoTargetGroup( @Args("scope", { type: () => Scope }, new DynamicDtoValidationPipe(Scope)) - scope: typeof Scope, + scope: EmailCampaignScopeInterface, @Args("input", { type: () => TargetGroupInput }, new DynamicDtoValidationPipe(TargetGroupInput)) input: TargetGroupInputInterface, ): Promise { const brevoId = await this.brevoApiContactsService.createBrevoContactList(input.title, scope); diff --git a/packages/api/brevo-api/src/types.ts b/packages/api/brevo-api/src/types.ts index 2b078912476..ef922d6f18b 100644 --- a/packages/api/brevo-api/src/types.ts +++ b/packages/api/brevo-api/src/types.ts @@ -4,5 +4,4 @@ export type BrevoContactAttributesInterface = Record; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type BrevoContactFilterAttributesInterface = Record | undefined>; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type EmailCampaignScopeInterface = Record; +export type EmailCampaignScopeInterface = Record; diff --git a/packages/api/cms-api/src/dam/files/dto/empty-dam-scope.ts b/packages/api/cms-api/src/dam/files/dto/empty-dam-scope.ts index be3bf6813d8..601acc766cb 100644 --- a/packages/api/cms-api/src/dam/files/dto/empty-dam-scope.ts +++ b/packages/api/cms-api/src/dam/files/dto/empty-dam-scope.ts @@ -6,7 +6,7 @@ import { DamScopeInterface } from "../../types"; @ObjectType("DamScope") @InputType("DamScopeInput") export class EmptyDamScope implements DamScopeInterface { - [key: string]: unknown; + [key: string]: string | number | null | undefined; // empty scope @Field({ nullable: true }) @IsUndefinable() diff --git a/packages/api/cms-api/src/dam/files/dto/find-copies-of-file-in-scope.args.ts b/packages/api/cms-api/src/dam/files/dto/find-copies-of-file-in-scope.args.ts index 5ecb7cad286..30ceff19e1e 100644 --- a/packages/api/cms-api/src/dam/files/dto/find-copies-of-file-in-scope.args.ts +++ b/packages/api/cms-api/src/dam/files/dto/find-copies-of-file-in-scope.args.ts @@ -21,7 +21,7 @@ export function createFindCopiesOfFileInScopeArgs({ Scope, hasNonEmptyScope }: { @Field(() => Scope, { defaultValue: hasNonEmptyScope ? undefined : {} }) @ValidateNested() - scope: typeof Scope; + scope: DamScopeInterface; @Field(() => ImageCropAreaInput, { nullable: true }) @ValidateNested() diff --git a/packages/api/cms-api/src/dam/files/entities/file.entity.ts b/packages/api/cms-api/src/dam/files/entities/file.entity.ts index a4d73d9923d..ae3584ddec4 100644 --- a/packages/api/cms-api/src/dam/files/entities/file.entity.ts +++ b/packages/api/cms-api/src/dam/files/entities/file.entity.ts @@ -180,7 +180,7 @@ export function createFileEntity({ Scope, Folder }: { Scope?: Type Scope) @Field(() => Scope) - scope: typeof Scope; + scope: DamScopeInterface; } return DamFile; } else { diff --git a/packages/api/cms-api/src/dam/files/entities/folder.entity.ts b/packages/api/cms-api/src/dam/files/entities/folder.entity.ts index 418a7f182a6..896824a8330 100644 --- a/packages/api/cms-api/src/dam/files/entities/folder.entity.ts +++ b/packages/api/cms-api/src/dam/files/entities/folder.entity.ts @@ -119,7 +119,7 @@ export function createFolderEntity({ Scope }: { Scope?: Type class DamFolder extends FolderBase { @Embedded(() => Scope) @Field(() => Scope) - scope: typeof Scope; + scope: DamScopeInterface; @Field(() => DamFolder, { nullable: true }) parent: DamFolder | null; diff --git a/packages/api/cms-api/src/dam/files/files.resolver.ts b/packages/api/cms-api/src/dam/files/files.resolver.ts index dc0a54eefcb..aa22c63b766 100644 --- a/packages/api/cms-api/src/dam/files/files.resolver.ts +++ b/packages/api/cms-api/src/dam/files/files.resolver.ts @@ -188,7 +188,7 @@ export function createFilesResolver({ @Query(() => Boolean) async damIsFilenameOccupied( @Args("filename") filename: string, - @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: typeof Scope, + @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: DamScopeInterface, @Args("folderId", { nullable: true }) folderId?: string, ): Promise { const extension = extname(filename); @@ -203,7 +203,7 @@ export function createFilesResolver({ @Query(() => [FilenameResponse]) async damAreFilenamesOccupied( @Args("filenames", { type: () => [FilenameInput] }) filenames: Array, - @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: typeof Scope, + @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: DamScopeInterface, ): Promise> { const response: Array = []; diff --git a/packages/api/cms-api/src/dam/files/folders.resolver.ts b/packages/api/cms-api/src/dam/files/folders.resolver.ts index 1f8be7fcff0..e7fd4f55c68 100644 --- a/packages/api/cms-api/src/dam/files/folders.resolver.ts +++ b/packages/api/cms-api/src/dam/files/folders.resolver.ts @@ -43,7 +43,7 @@ export function createFoldersResolver({ @Query(() => [Folder]) async damFoldersFlat( - @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: typeof Scope, + @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: DamScopeInterface, ): Promise { return this.foldersService.findAllFlat(nonEmptyScopeOrNothing(scope)); } @@ -75,7 +75,7 @@ export function createFoldersResolver({ @SkipBuild() async createDamFolder( @Args("input", { type: () => CreateFolderInput }) input: CreateFolderInput, - @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: typeof Scope, + @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: DamScopeInterface, ): Promise { return this.foldersService.create(input, nonEmptyScopeOrNothing(scope)); } @@ -95,7 +95,7 @@ export function createFoldersResolver({ async moveDamFolders( @Args("folderIds", { type: () => [ID] }) folderIds: string[], @Args("targetFolderId", { type: () => ID, nullable: true }) targetFolderId: string, - @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: typeof Scope, + @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: DamScopeInterface, ): Promise { return this.foldersService.moveBatch({ folderIds, targetFolderId }, nonEmptyScopeOrNothing(scope)); } diff --git a/packages/api/cms-api/src/dam/types.ts b/packages/api/cms-api/src/dam/types.ts index 602f2e977af..7ac739a3c6f 100644 --- a/packages/api/cms-api/src/dam/types.ts +++ b/packages/api/cms-api/src/dam/types.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type DamScopeInterface = Record; +type DamScopeInterface = Record; export type { DamScopeInterface }; diff --git a/packages/api/cms-api/src/page-tree/blocks/internal-link-block-transformer.service.ts b/packages/api/cms-api/src/page-tree/blocks/internal-link-block-transformer.service.ts index bca5f917c3c..70b31c1ff95 100644 --- a/packages/api/cms-api/src/page-tree/blocks/internal-link-block-transformer.service.ts +++ b/packages/api/cms-api/src/page-tree/blocks/internal-link-block-transformer.service.ts @@ -2,6 +2,7 @@ import { Injectable } from "@nestjs/common"; import { BlockTransformerServiceInterface } from "../../blocks/block"; import { PageTreeReadApiService } from "../page-tree-read-api.service"; +import type { ScopeInterface } from "../types"; import type { InternalLinkBlockData } from "./internal-link.block"; type TransformResponse = { @@ -10,7 +11,7 @@ type TransformResponse = { name: string; path: string; documentType: string; - scope: Record | null; + scope: ScopeInterface | null; } | null; targetPageAnchor?: string; }; diff --git a/packages/api/cms-api/src/page-tree/dto/empty-page-tree-node-scope.ts b/packages/api/cms-api/src/page-tree/dto/empty-page-tree-node-scope.ts index 2d9b52d48d8..abd666f4728 100644 --- a/packages/api/cms-api/src/page-tree/dto/empty-page-tree-node-scope.ts +++ b/packages/api/cms-api/src/page-tree/dto/empty-page-tree-node-scope.ts @@ -5,7 +5,7 @@ import { ScopeInterface } from "../types"; @ObjectType("PageTreeNodeScope") @InputType("PageTreeNodeScopeInput") export class EmptyPageTreeNodeScope implements ScopeInterface { - [key: string]: unknown; + [key: string]: string | number | null | undefined; // empty scope @Field({ nullable: true }) thisScopeHasNoFields____?: string; // just anything so this class has at least one field and can be interpreted as a gql-object/input type diff --git a/packages/api/cms-api/src/page-tree/types.ts b/packages/api/cms-api/src/page-tree/types.ts index 03f8a6a5fe3..c56fcb1e730 100644 --- a/packages/api/cms-api/src/page-tree/types.ts +++ b/packages/api/cms-api/src/page-tree/types.ts @@ -3,8 +3,7 @@ import { registerEnumType } from "@nestjs/graphql"; import type { PageTreeNodeBaseCreateInput, PageTreeNodeBaseUpdateInput } from "./dto/page-tree-node.input"; import type { PageTreeNodeBase } from "./entities/page-tree-node-base.entity"; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type ScopeInterface = Record; //@TODO: move to general scope (other modules (redirect, dam) need this too) +export type ScopeInterface = Record; //@TODO: move to general scope (other modules (redirect, dam) need this too) export type PageTreeNodeCategory = string; export type PageTreeNodeInterface = PageTreeNodeBase & { scope?: ScopeInterface }; export type PageTreeNodeCreateInputInterface = PageTreeNodeBaseCreateInput; diff --git a/packages/api/cms-api/src/redirects/dto/empty-redirect-scope.ts b/packages/api/cms-api/src/redirects/dto/empty-redirect-scope.ts index 6b4ab54e4d3..3774febdbeb 100644 --- a/packages/api/cms-api/src/redirects/dto/empty-redirect-scope.ts +++ b/packages/api/cms-api/src/redirects/dto/empty-redirect-scope.ts @@ -5,7 +5,7 @@ import { RedirectScopeInterface } from "../types"; @ObjectType("RedirectScope") @InputType("RedirectScopeInput") export class EmptyRedirectScope implements RedirectScopeInterface { - [key: string]: unknown; + [key: string]: string | number | null | undefined; // empty scope @Field({ nullable: true }) thisScopeHasNoFields____?: string; // just anything so this class has at least one field and can be interpreted as a gql-object/input type diff --git a/packages/api/cms-api/src/redirects/entities/redirect-entity.factory.ts b/packages/api/cms-api/src/redirects/entities/redirect-entity.factory.ts index 1476d4d8429..fffae410a3f 100644 --- a/packages/api/cms-api/src/redirects/entities/redirect-entity.factory.ts +++ b/packages/api/cms-api/src/redirects/entities/redirect-entity.factory.ts @@ -97,7 +97,7 @@ export class RedirectEntityFactory { class Redirect extends RedirectBase { @Embedded(() => RedirectScope) @Field(() => RedirectScope) - scope: typeof RedirectScope; + scope: RedirectScopeInterface; } return Redirect; } else { diff --git a/packages/api/cms-api/src/redirects/redirects.resolver.ts b/packages/api/cms-api/src/redirects/redirects.resolver.ts index 03743e98da6..2b4e1433782 100644 --- a/packages/api/cms-api/src/redirects/redirects.resolver.ts +++ b/packages/api/cms-api/src/redirects/redirects.resolver.ts @@ -184,7 +184,7 @@ export function createRedirectsResolver({ @Query(() => Redirect, { nullable: true }) async redirectBySource( - @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: typeof Scope, + @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: RedirectScopeInterface, @Args("source", { type: () => String }) source: string, @Args("sourceType", { type: () => RedirectSourceType }) sourceType: RedirectSourceType, ): Promise { @@ -198,7 +198,7 @@ export function createRedirectsResolver({ @Query(() => Boolean) async redirectSourceAvailable( - @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: typeof Scope, + @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }) scope: RedirectScopeInterface, @Args("source", { type: () => String }) source: string, ): Promise { return this.redirectService.isRedirectSourceAvailable(source, nonEmptyScopeOrNothing(scope)); @@ -207,7 +207,7 @@ export function createRedirectsResolver({ @Mutation(() => Redirect) async createRedirect( @Args("scope", { type: () => Scope, defaultValue: hasNonEmptyScope ? undefined : {} }, new DynamicDtoValidationPipe(Scope)) - scope: typeof Scope, + scope: RedirectScopeInterface, @Args("input", { type: () => RedirectInput }, new DynamicDtoValidationPipe(RedirectInput)) input: RedirectInputInterface, ): Promise { if (!(await this.redirectService.isRedirectSourceAvailable(input.source, nonEmptyScopeOrNothing(scope)))) { diff --git a/packages/api/cms-api/src/redirects/types.ts b/packages/api/cms-api/src/redirects/types.ts index 8c56f714f1a..7aa4e03dde0 100644 --- a/packages/api/cms-api/src/redirects/types.ts +++ b/packages/api/cms-api/src/redirects/types.ts @@ -1,2 +1 @@ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type RedirectScopeInterface = Record; //@TODO: move to general scope (other modules (page-tree, dam) need this too) +export type RedirectScopeInterface = Record; //@TODO: move to general scope (other modules (page-tree, dam) need this too) diff --git a/packages/site/site-nextjs/src/sitePreview/previewUtils.ts b/packages/site/site-nextjs/src/sitePreview/previewUtils.ts index e0e0f2b22cc..2379ae888d9 100644 --- a/packages/site/site-nextjs/src/sitePreview/previewUtils.ts +++ b/packages/site/site-nextjs/src/sitePreview/previewUtils.ts @@ -5,8 +5,7 @@ import { cookies, draftMode, headers as getHeaders } from "next/headers"; // Return type of previewParams function type PreviewParams = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - scope: Record; + scope: Record; previewData?: PreviewData; };