From 1e55a779e61594fe0d94d0d5b3f3f3d936e39a61 Mon Sep 17 00:00:00 2001 From: Thomas Dax Date: Thu, 23 May 2024 09:14:07 +0200 Subject: [PATCH 01/11] Extend dependencies docs (#1957) Extends the docs about dependencies: - add more background info on what the block index is for - Restructure parts of the docs - Add info on how to - add field resolvers - use the `DependencyList` --- https://deploy-preview-1957--comet-dxp-docs.netlify.app/docs/dependencies/ --- docs/docs/dependencies/index.md | 208 +++++++++++++++--- .../docs/migration/migration-from-v5-to-v6.md | 37 ++++ 2 files changed, 213 insertions(+), 32 deletions(-) diff --git a/docs/docs/dependencies/index.md b/docs/docs/dependencies/index.md index b34de8a7b2..97d5ecfe00 100644 --- a/docs/docs/dependencies/index.md +++ b/docs/docs/dependencies/index.md @@ -1,40 +1,77 @@ -# Dependencies +# Block Index / Dependencies -## Register fields to be added to the block index +Blocks can have references to entities. +But since block data is stored as JSON, there is no actual database relationship. -### Add `@RootBlockEntity()` and `@RootBlock()` decorators to entity +If you still need to know which entities a block references or in which blocks an entity is used, you can use COMET's block index. -The entity needs to be annotated with `@RootBlockEntity()`. +--- -All fields containing block data need to be annotated with `@RootBlock()`. The Block used by this field must be passed as an argument. +## Configuration Guide -```ts -//... -@RootBlockEntity() -export class News extends BaseEntity { - // ... +Follow the upcoming guide if you want to - @RootBlock(DamImageBlock) - @Property({ customType: new RootBlockType(DamImageBlock) }) - @Field(() => RootBlockDataScalar(DamImageBlock)) - image: BlockDataInterface; +- make the "Dependents" tab in the DAM work +- display the dependencies or dependents of an entity somewhere in your admin app - @RootBlock(NewsContentBlock) - @Property({ customType: new RootBlockType(NewsContentBlock) }) - @Field(() => RootBlockDataScalar(NewsContentBlock)) - content: BlockDataInterface; +### Configuring the block index - // ... -} +#### 1. API: Register fields to be added to the block index + +First, you must "tell" the block index which database fields contain block data. +It will scan these fields for dependency information. + +To do that, you must + +- Annotate the entity with `@RootBlockEntity()` +- Annotate all columns containing block data with `@RootBlock(ExampleBlock)` + +For example: + +```diff ++ @RootBlockEntity() + export class News extends BaseEntity { + // ... + ++ @RootBlock(DamImageBlock) + @Property({ customType: new RootBlockType(DamImageBlock) }) + @Field(() => RootBlockDataScalar(DamImageBlock)) + image: BlockDataInterface; + ++ @RootBlock(NewsContentBlock) + @Property({ customType: new RootBlockType(NewsContentBlock) }) + @Field(() => RootBlockDataScalar(NewsContentBlock)) + content: BlockDataInterface; + + // ... + } ``` -## Correctly display a dependency target +#### 2. Create the block index + +You must then create the block index by calling `npm run console createBlockIndexViews` in your `/api` directory. +This creates a materialized view called `block_index_dependencies` in your database. -### 1. Add `@EntityInfo()` to entity (API) +You must recreate the block index views after -You can provide the entity info in two ways: +- executing database migrations +- executing the fixtures (because they drop the whole database and recreate it) -#### `GetEntityInfo` Method +You can automate this process by following the steps in the [migration guide](../migration/migration-from-v5-to-v6/#block-index). +For new projects, it should already be automated. + +### Displaying dependencies in the admin interface + +Next, you probably want to display the dependencies or dependents (usages) of an entity in the admin interface. + +#### 1. API: Add `@EntityInfo()` to entity + +The `@EntityInfo()` decorator allows you to configure which information about an entity should be displayed in the admin interface. +You can provide a `name` and `secondaryInformation`. + +The decorator accepts two inputs: + +##### `GetEntityInfo` method The simple way is to provide a function returning a `name` and (optional) `secondaryInformation` based on the entity instance. @@ -43,9 +80,10 @@ The simple way is to provide a function returning a `name` and (optional) `secon @EntityInfo((news) => ({ name: news.title, secondaryInformation: news.slug })) ``` -#### `EntityInfoService` +##### `EntityInfoService` -If you need to load additional information from a service or repository to provide the entity info, you can implement an `EntityInfoService`. In this service, you can use Nest's dependency injection. +If you need to load additional information from a service or repository, you can implement an `EntityInfoService`. +In this service, you can use Nest's dependency injection. The service must offer a `getEntityInfo()` method returning a `name` and (optional) `secondaryInformation`. @@ -69,11 +107,29 @@ export class FilesEntityInfoService implements EntityInfoServiceInterface implements DocumentInterface { + // ... +} +``` + +#### 2. Admin: Implement the `DependencyInterface` + +The DependencyInterface requires a translatable `displayName` and a `resolvePath()` method. +`resolvePath` provides a URL path to the edit page of an entity or a specific block. + +
+ +Example of a `resolvePath` method + +```tsx // NewsDependency.tsx export const NewsDependency: DependencyInterface = { displayName: , @@ -112,7 +168,23 @@ export const NewsDependency: DependencyInterface = { }; ``` -You may also use the `createDependencyMethods` helper to simplify resolving the path to the entity/block. Use the `basePath` option to specify where the entity is located in the Admin. +
+ +Usually, you don't have to write the `resolvePath` method yourself. +Instead, use one of our helpers: + +##### `createDependencyMethods` + +For most entities, you can use the `createDependencyMethods` helper. + +The `rootQueryName` specifies the name of the GraphQL query used to load the entity. +It should normally be the camelCase version of the entity name. + +The `rootBlocks` specify which of the entity's fields contain block data. +These should be the same fields you annotated with `@RootBlock()` in the API. +You must also specify the used Block and - if necessary - the path under which the block is available. + +The `basePath` option specifies the URL path to the entity's edit page. ```tsx // NewsDependency.tsx @@ -130,7 +202,10 @@ export const NewsDependency: DependencyInterface = { }; ``` -For document types you may use the `createDocumentDependencyMethods` helper that also loads the page tree node the document is attached to: +##### `createDocumentDependencyMethods` + +For document types you can use the `createDocumentDependencyMethods` helper. +It loads the document and also the `PageTreeNode` the document is attached to. ```tsx // Page.tsx @@ -153,11 +228,11 @@ export const Page: DocumentInterface, GQLPageIn }; ``` -### 3. Register the DependencyInterface at the DependenciesConfigProvider +#### 3. Admin: Register the `DependencyInterface` at the `DependenciesConfigProvider` The key must be the name of the GraphQL object type associated with the entity. -```ts +```tsx // App.tsx // ... // ... ``` + +Now, the DAM's "Dependents" tab should work. +If that was your goal, you can stop here. +Otherwise, continue following the guide. + +#### 4. API: Add field resolvers + +If you want to query the dependencies or dependents of an entity, use the factories provided by the library. +**Only do this where it makes sense.** + +```ts +// news.module.ts +@Module({ + // ... + providers: [ + // ... + DependenciesResolverFactory.create(News), + DependentsResolverFactory.create(News), + ], +}) +export class NewsModule {} +``` + +#### 5. Admin: Display dependencies with the `DependencyList` component + +You can use the `DependencyList` component provided by `@comet/cms-admin` to display dependencies or dependents. +The DAM uses this component in its "Dependents" tab. + +The component requires two props: + +- `query`: A GraphQL query. It must have a `dependencies` or `dependents` field resolver. +- `variables`: The variables for the query. + +
+ +A usage could look like this + +```tsx + +``` + +
diff --git a/docs/docs/migration/migration-from-v5-to-v6.md b/docs/docs/migration/migration-from-v5-to-v6.md index 9013d63ad5..c38b9d4f79 100644 --- a/docs/docs/migration/migration-from-v5-to-v6.md +++ b/docs/docs/migration/migration-from-v5-to-v6.md @@ -160,6 +160,43 @@ It automatically installs the new versions of all `@comet` libraries, runs an ES }), ``` +### Block Index + +Automate the creation of the block index during local development: + +1. Call `DependenciesService#createViews` in your `FixturesConsole`: + +```diff + // ... + await this.publicUploadsFixtureService.generatePublicUploads(); + ++ await this.dependenciesService.createViews(); + + await this.orm.em.flush(); + // ... +``` + +2. Call `createBlockIndexViews` before starting the API (after the migrations): + +Remove `db:migrate` from `dev-pm.config.js`: + +```diff +{ + name: "api", +- script: "npm --prefix api run db:migrate && npm --prefix api run start:dev", ++ script: "npm --prefix api run start:dev", + group: "api", + waitOn: ["tcp:$POSTGRESQL_PORT", "tcp:$IMGPROXY_PORT"], +}, +``` + +Add `db:migrate` and `createBlockIndexViews` to `start:dev` script in package.json: + +```diff +- "start:dev": "npm run prebuild && dotenv -c secrets -- nest start --watch --preserveWatchOutput", ++ "start:dev": "npm run prebuild && npm run db:migrate && npm run console createBlockIndexViews && dotenv -c secrets -- nest start --watch --preserveWatchOutput", +``` + ## Admin ### User Permissions From 815ba51e70db14ca8d9c96eaf5ce843b6a3848ce Mon Sep 17 00:00:00 2001 From: Thomas Dax Date: Thu, 23 May 2024 14:20:49 +0200 Subject: [PATCH 02/11] Fix link target validation in `ExternalLinkBlock` (#2102) ## Problem: The validation in the ExternalLinkBlock was broken. The `isValid` check uses `isLinkTarget` for validation: https://github.com/vivid-planet/comet/blob/9867242fa6bad4849de58ccce1c28b816cc109a3/packages/admin/cms-admin/src/blocks/ExternalLinkBlock.tsx#L42-L44 The `targetUrl` field uses `validateUrl`: https://github.com/vivid-planet/comet/blob/9867242fa6bad4849de58ccce1c28b816cc109a3/packages/admin/cms-admin/src/blocks/ExternalLinkBlock.tsx#L66-L73 The problem was that `validateUrl` internally used `isUrl` from class validator. `isUrl` is less strict than `isLinkTarget`. This resulted in no validation error in the UI but an error when trying to save the block: https://github.com/vivid-planet/comet/assets/13380047/a6c91ce1-94a5-4aac-a755-ef497a506504 ## Solution: I added `validateLinkTarget` using the stricter `isLinkTarget` check. --- .changeset/six-meals-lie.md | 8 ++++++++ .../admin/cms-admin/src/blocks/ExternalLinkBlock.tsx | 4 ++-- .../cms-admin/src/validation/validateLinkTarget.tsx | 10 ++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 .changeset/six-meals-lie.md create mode 100644 packages/admin/cms-admin/src/validation/validateLinkTarget.tsx diff --git a/.changeset/six-meals-lie.md b/.changeset/six-meals-lie.md new file mode 100644 index 0000000000..69f6b4c99e --- /dev/null +++ b/.changeset/six-meals-lie.md @@ -0,0 +1,8 @@ +--- +"@comet/cms-admin": patch +--- + +Fix link target validation in `ExternalLinkBlock` + +Previously, two different validation checks were used. +This resulted in an error when saving an invalid link target but no error message was shown. diff --git a/packages/admin/cms-admin/src/blocks/ExternalLinkBlock.tsx b/packages/admin/cms-admin/src/blocks/ExternalLinkBlock.tsx index df14ba3fa2..71b16f2c93 100644 --- a/packages/admin/cms-admin/src/blocks/ExternalLinkBlock.tsx +++ b/packages/admin/cms-admin/src/blocks/ExternalLinkBlock.tsx @@ -6,7 +6,7 @@ import { FormattedMessage } from "react-intl"; import { ExternalLinkBlockData, ExternalLinkBlockInput } from "../blocks.generated"; import { isLinkTarget } from "../validation/isLinkTarget"; -import { validateUrl } from "../validation/validateUrl"; +import { validateLinkTarget } from "../validation/validateLinkTarget"; type State = ExternalLinkBlockData; @@ -68,7 +68,7 @@ export const ExternalLinkBlock: BlockInterface validateUrl(url)} + validate={(url) => validateLinkTarget(url)} disableContentTranslation /> diff --git a/packages/admin/cms-admin/src/validation/validateLinkTarget.tsx b/packages/admin/cms-admin/src/validation/validateLinkTarget.tsx new file mode 100644 index 0000000000..a6a702762f --- /dev/null +++ b/packages/admin/cms-admin/src/validation/validateLinkTarget.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import { isLinkTarget } from "./isLinkTarget"; + +export function validateLinkTarget(linkTarget: string) { + if (!isLinkTarget(linkTarget)) { + return ; + } +} From 984ebc94106dd6510651e079451b5d424e7ea6c2 Mon Sep 17 00:00:00 2001 From: Thomas Dax Date: Thu, 23 May 2024 22:13:57 +0200 Subject: [PATCH 03/11] Fix ExternalLinkBlock (#2106) In #2102 I missed that the url can be undefined because it has an any type and the typing of validateUrl was also incorrect. Now the `ExternalLinkBlock` breaks when the URL is emptied. This PR fixes that by checking if url is defined. --- .../admin/cms-admin/src/validation/validateLinkTarget.tsx | 4 ++-- packages/admin/cms-admin/src/validation/validateUrl.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/admin/cms-admin/src/validation/validateLinkTarget.tsx b/packages/admin/cms-admin/src/validation/validateLinkTarget.tsx index a6a702762f..c91928f12b 100644 --- a/packages/admin/cms-admin/src/validation/validateLinkTarget.tsx +++ b/packages/admin/cms-admin/src/validation/validateLinkTarget.tsx @@ -3,8 +3,8 @@ import { FormattedMessage } from "react-intl"; import { isLinkTarget } from "./isLinkTarget"; -export function validateLinkTarget(linkTarget: string) { - if (!isLinkTarget(linkTarget)) { +export function validateLinkTarget(linkTarget?: string) { + if (linkTarget && !isLinkTarget(linkTarget)) { return ; } } diff --git a/packages/admin/cms-admin/src/validation/validateUrl.tsx b/packages/admin/cms-admin/src/validation/validateUrl.tsx index 93167403f9..c82d3dcaf7 100644 --- a/packages/admin/cms-admin/src/validation/validateUrl.tsx +++ b/packages/admin/cms-admin/src/validation/validateUrl.tsx @@ -2,7 +2,7 @@ import { isURL } from "class-validator"; import React from "react"; import { FormattedMessage } from "react-intl"; -export function validateUrl(url: string) { +export function validateUrl(url?: string) { if (url && !isURL(url)) { return ; } From 90f348017f1850096f94821334943d872abc213a Mon Sep 17 00:00:00 2001 From: Daniel Karnutsch Date: Mon, 27 May 2024 15:00:16 +0200 Subject: [PATCH 04/11] Refactor Kubernetes local mode (#2094) - Use Guard to avoid code duplication - Use prevention everywhere for consistent behavior - Better error message --------- Co-authored-by: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> --- .../src/builds/build-templates.resolver.ts | 10 ++++------ .../src/builds/build-templates.service.ts | 3 ++- .../api/cms-api/src/builds/builds.resolver.ts | 4 +++- .../api/cms-api/src/builds/builds.service.ts | 12 ------------ .../src/cron-jobs/cron-jobs.resolver.ts | 12 +++--------- .../cms-api/src/cron-jobs/jobs.resolver.ts | 16 +++------------- .../prevent-local-invocation.guard.ts | 19 +++++++++++++++++++ 7 files changed, 34 insertions(+), 42 deletions(-) create mode 100644 packages/api/cms-api/src/kubernetes/prevent-local-invocation.guard.ts diff --git a/packages/api/cms-api/src/builds/build-templates.resolver.ts b/packages/api/cms-api/src/builds/build-templates.resolver.ts index 5bdf670771..a1907e0caa 100644 --- a/packages/api/cms-api/src/builds/build-templates.resolver.ts +++ b/packages/api/cms-api/src/builds/build-templates.resolver.ts @@ -1,8 +1,9 @@ +import { UseGuards } from "@nestjs/common"; import { Query, Resolver } from "@nestjs/graphql"; import { GetCurrentUser } from "../auth/decorators/get-current-user.decorator"; import { LABEL_ANNOTATION } from "../kubernetes/kubernetes.constants"; -import { KubernetesService } from "../kubernetes/kubernetes.service"; +import { PreventLocalInvocationGuard } from "../kubernetes/prevent-local-invocation.guard"; import { RequiredPermission } from "../user-permissions/decorators/required-permission.decorator"; import { CurrentUser } from "../user-permissions/dto/current-user"; import { BuildTemplatesService } from "./build-templates.service"; @@ -10,15 +11,12 @@ import { BuildTemplateObject } from "./dto/build-template.object"; @Resolver(() => BuildTemplateObject) @RequiredPermission(["builds"], { skipScopeCheck: true }) // Scopes are checked in Code +@UseGuards(PreventLocalInvocationGuard) export class BuildTemplatesResolver { - constructor(private readonly kubernetesService: KubernetesService, private readonly buildTemplatesService: BuildTemplatesService) {} + constructor(private readonly buildTemplatesService: BuildTemplatesService) {} @Query(() => [BuildTemplateObject]) async buildTemplates(@GetCurrentUser() user: CurrentUser): Promise { - if (this.kubernetesService.localMode) { - throw Error("Not available in local mode!"); - } - const builderCronJobs = await this.buildTemplatesService.getAllowedBuilderCronJobs(user); return builderCronJobs.map((cronJob) => ({ id: cronJob.metadata?.uid as string, diff --git a/packages/api/cms-api/src/builds/build-templates.service.ts b/packages/api/cms-api/src/builds/build-templates.service.ts index af2cebec55..e54426479c 100644 --- a/packages/api/cms-api/src/builds/build-templates.service.ts +++ b/packages/api/cms-api/src/builds/build-templates.service.ts @@ -16,7 +16,8 @@ export class BuildTemplatesService { ) {} async getAllowedBuilderCronJobs(user: CurrentUser): Promise { - return (await this.getAllBuilderCronJobs()).filter((cronJob) => { + const builderCronJobs = await this.getAllBuilderCronJobs(); + return builderCronJobs.filter((cronJob) => { return this.accessControlService.isAllowed(user, "builds", this.kubernetesService.getContentScope(cronJob) ?? {}); }); } diff --git a/packages/api/cms-api/src/builds/builds.resolver.ts b/packages/api/cms-api/src/builds/builds.resolver.ts index adcf0c8308..6987e1ab9b 100644 --- a/packages/api/cms-api/src/builds/builds.resolver.ts +++ b/packages/api/cms-api/src/builds/builds.resolver.ts @@ -1,10 +1,11 @@ import { V1CronJob } from "@kubernetes/client-node"; -import { Inject } from "@nestjs/common"; +import { Inject, UseGuards } from "@nestjs/common"; import { Args, Mutation, Query, Resolver } from "@nestjs/graphql"; import { GetCurrentUser } from "../auth/decorators/get-current-user.decorator"; import { INSTANCE_LABEL } from "../kubernetes/kubernetes.constants"; import { KubernetesService } from "../kubernetes/kubernetes.service"; +import { PreventLocalInvocationGuard } from "../kubernetes/prevent-local-invocation.guard"; import { RequiredPermission } from "../user-permissions/decorators/required-permission.decorator"; import { CurrentUser } from "../user-permissions/dto/current-user"; import { ACCESS_CONTROL_SERVICE } from "../user-permissions/user-permissions.constants"; @@ -17,6 +18,7 @@ import { SkipBuild } from "./skip-build.decorator"; @Resolver(() => Build) @RequiredPermission(["builds"], { skipScopeCheck: true }) // Scopes are checked in code +@UseGuards(PreventLocalInvocationGuard) export class BuildsResolver { constructor( private readonly kubernetesService: KubernetesService, diff --git a/packages/api/cms-api/src/builds/builds.service.ts b/packages/api/cms-api/src/builds/builds.service.ts index c29e02c57d..2834756fab 100644 --- a/packages/api/cms-api/src/builds/builds.service.ts +++ b/packages/api/cms-api/src/builds/builds.service.ts @@ -37,10 +37,6 @@ export class BuildsService { } async createBuilds(trigger: string, builderCronJobs: V1CronJob[]): Promise { - if (this.kubernetesService.localMode) { - throw Error("Not available in local mode!"); - } - // No ACL here, because this is only used for clean-up const builderJobs = await this.kubernetesService.getAllJobs( `${BUILDER_LABEL} = true, ${INSTANCE_LABEL} = ${this.kubernetesService.helmRelease}`, @@ -90,10 +86,6 @@ export class BuildsService { } async getBuilds(user: CurrentUser, options?: { limit?: number | undefined }): Promise { - if (this.kubernetesService.localMode) { - throw Error("Not available in local mode!"); - } - const buildJobs = await this.getAllowedBuildJobs(user); return Promise.all( buildJobs.slice(0, options?.limit).map(async (job) => { @@ -115,10 +107,6 @@ export class BuildsService { } async getAutoBuildStatus(user: CurrentUser): Promise { - if (this.kubernetesService.localMode) { - throw Error("Not available in local mode!"); - } - const autoBuildStatus = new AutoBuildStatus(); autoBuildStatus.hasChangesSinceLastBuild = await this.hasChangesSinceLastBuild(); diff --git a/packages/api/cms-api/src/cron-jobs/cron-jobs.resolver.ts b/packages/api/cms-api/src/cron-jobs/cron-jobs.resolver.ts index 9114677515..732e05ea7e 100644 --- a/packages/api/cms-api/src/cron-jobs/cron-jobs.resolver.ts +++ b/packages/api/cms-api/src/cron-jobs/cron-jobs.resolver.ts @@ -1,4 +1,4 @@ -import { Inject } from "@nestjs/common"; +import { Inject, UseGuards } from "@nestjs/common"; import { Args, Mutation, Parent, Query, ResolveField, Resolver } from "@nestjs/graphql"; import { format } from "date-fns"; @@ -8,6 +8,7 @@ import { SkipBuild } from "../builds/skip-build.decorator"; import { KubernetesJobStatus } from "../kubernetes/job-status.enum"; import { INSTANCE_LABEL } from "../kubernetes/kubernetes.constants"; import { KubernetesService } from "../kubernetes/kubernetes.service"; +import { PreventLocalInvocationGuard } from "../kubernetes/prevent-local-invocation.guard"; import { RequiredPermission } from "../user-permissions/decorators/required-permission.decorator"; import { CurrentUser } from "../user-permissions/dto/current-user"; import { ACCESS_CONTROL_SERVICE } from "../user-permissions/user-permissions.constants"; @@ -19,6 +20,7 @@ import { JobsService } from "./jobs.service"; @Resolver(() => CronJob) @RequiredPermission(["cronJobs"], { skipScopeCheck: true }) +@UseGuards(PreventLocalInvocationGuard) export class CronJobsResolver { constructor( private readonly kubernetesService: KubernetesService, @@ -29,10 +31,6 @@ export class CronJobsResolver { @Query(() => [CronJob]) async kubernetesCronJobs(@GetCurrentUser() user: CurrentUser): Promise { - if (this.kubernetesService.localMode) { - throw Error("Not available in local mode!"); - } - const cronJobs = await this.kubernetesService.getAllCronJobs( `${INSTANCE_LABEL} = ${this.kubernetesService.helmRelease}, ${BUILDER_LABEL} != true`, ); @@ -50,10 +48,6 @@ export class CronJobsResolver { @Query(() => CronJob) async kubernetesCronJob(@Args("name") name: string, @GetCurrentUser() user: CurrentUser): Promise { - if (this.kubernetesService.localMode) { - throw Error("Not available in local mode!"); - } - const cronJob = await this.kubernetesService.getCronJob(name); const contentScope = this.kubernetesService.getContentScope(cronJob); if (contentScope && !this.accessControlService.isAllowed(user, "builds", contentScope)) { diff --git a/packages/api/cms-api/src/cron-jobs/jobs.resolver.ts b/packages/api/cms-api/src/cron-jobs/jobs.resolver.ts index 0e6d310784..39ae74a874 100644 --- a/packages/api/cms-api/src/cron-jobs/jobs.resolver.ts +++ b/packages/api/cms-api/src/cron-jobs/jobs.resolver.ts @@ -1,8 +1,9 @@ -import { Inject } from "@nestjs/common"; +import { Inject, UseGuards } from "@nestjs/common"; import { Args, Query, Resolver } from "@nestjs/graphql"; import { GetCurrentUser } from "../auth/decorators/get-current-user.decorator"; import { KubernetesService } from "../kubernetes/kubernetes.service"; +import { PreventLocalInvocationGuard } from "../kubernetes/prevent-local-invocation.guard"; import { RequiredPermission } from "../user-permissions/decorators/required-permission.decorator"; import { CurrentUser } from "../user-permissions/dto/current-user"; import { ACCESS_CONTROL_SERVICE } from "../user-permissions/user-permissions.constants"; @@ -12,6 +13,7 @@ import { JobsService } from "./jobs.service"; @Resolver(() => Job) @RequiredPermission(["cronJobs"], { skipScopeCheck: true }) +@UseGuards(PreventLocalInvocationGuard) export class JobsResolver { constructor( private readonly kubernetesService: KubernetesService, @@ -21,10 +23,6 @@ export class JobsResolver { @Query(() => [Job]) async kubernetesJobs(@Args("cronJobName") cronJobName: string, @GetCurrentUser() user: CurrentUser): Promise { - if (this.kubernetesService.localMode) { - throw Error("Not available in local mode!"); - } - const cronJob = await this.kubernetesService.getCronJob(cronJobName); const contentScope = this.kubernetesService.getContentScope(cronJob); if (contentScope && !this.accessControlService.isAllowed(user, "cronJobs", contentScope)) { @@ -37,10 +35,6 @@ export class JobsResolver { @Query(() => Job) async kubernetesJob(@Args("name") jobName: string, @GetCurrentUser() user: CurrentUser): Promise { - if (this.kubernetesService.localMode) { - throw Error("Not available in local mode!"); - } - const job = await this.kubernetesService.getJob(jobName); const contentScope = this.kubernetesService.getContentScope(job); if (contentScope && !this.accessControlService.isAllowed(user, "cronJobs", contentScope)) { @@ -52,10 +46,6 @@ export class JobsResolver { @Query(() => String) async kubernetesJobLogs(@Args("name") jobName: string, @GetCurrentUser() user: CurrentUser): Promise { - if (this.kubernetesService.localMode) { - throw Error("Not available in local mode!"); - } - const job = await this.kubernetesService.getJob(jobName); const contentScope = this.kubernetesService.getContentScope(job); if (contentScope && !this.accessControlService.isAllowed(user, "cronJobs", contentScope)) { diff --git a/packages/api/cms-api/src/kubernetes/prevent-local-invocation.guard.ts b/packages/api/cms-api/src/kubernetes/prevent-local-invocation.guard.ts new file mode 100644 index 0000000000..83408c70f7 --- /dev/null +++ b/packages/api/cms-api/src/kubernetes/prevent-local-invocation.guard.ts @@ -0,0 +1,19 @@ +import { CanActivate, Injectable, Logger } from "@nestjs/common"; + +import { KubernetesService } from "./kubernetes.service"; + +@Injectable() +export class PreventLocalInvocationGuard implements CanActivate { + private readonly logger = new Logger(PreventLocalInvocationGuard.name); + + constructor(private readonly kubernetesService: KubernetesService) {} + + canActivate(): boolean { + if (this.kubernetesService.localMode) { + this.logger.warn("Local invocation not allowed, because the handler is related to build and/or Kubernetes"); + return false; + } + + return true; + } +} From fdf9fa7cbc325a9b542726909f86b1869a1a9809 Mon Sep 17 00:00:00 2001 From: fichtnerma <72387685+fichtnerma@users.noreply.github.com> Date: Mon, 27 May 2024 16:31:03 +0200 Subject: [PATCH 05/11] Change default value of automatic redirects checkbox based on page visibility (#2108) Change the Behaviour of the Automatic Redirect to default to false if the corresponding Page is unpublished or archived. Co-authored-by: Markus Fichtner --- .changeset/few-roses-worry.md | 5 +++++ .../admin/cms-admin/src/pages/createEditPageNode.tsx | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .changeset/few-roses-worry.md diff --git a/.changeset/few-roses-worry.md b/.changeset/few-roses-worry.md new file mode 100644 index 0000000000..e168ffa93d --- /dev/null +++ b/.changeset/few-roses-worry.md @@ -0,0 +1,5 @@ +--- +"@comet/cms-admin": minor +--- + +Automatic Redirects are now set to false if the page is unpublished or archived diff --git a/packages/admin/cms-admin/src/pages/createEditPageNode.tsx b/packages/admin/cms-admin/src/pages/createEditPageNode.tsx index 7da2484e01..8bd6953f9f 100644 --- a/packages/admin/cms-admin/src/pages/createEditPageNode.tsx +++ b/packages/admin/cms-admin/src/pages/createEditPageNode.tsx @@ -66,6 +66,7 @@ export function createEditPageNode({ hideInMenu parentId numberOfDescendants + visibility document { ... on DocumentInterface { id @@ -190,6 +191,8 @@ export function createEditPageNode({ } }; + const isActivePage = data?.page?.visibility === "Published"; + // Use `p-debounce` instead of `use-debounce` // because Final Form expects all validate calls to be resolved. // `p-debounce` resolves all calls, `use-debounce` doesn't @@ -384,7 +387,11 @@ export function createEditPageNode({ } > - + {(props) => ( Date: Tue, 28 May 2024 16:04:23 +0200 Subject: [PATCH 06/11] YouTubeVideoBlock: Fix `youtubeIdentifier` Type (#2121) - Fix type of youtubeIdentifier (was required, actually is optional) - Don't show validation error if youtubeIdentifier is empty - Allow emptying youtubeIdentifier --------- Co-authored-by: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> --- .changeset/real-plants-rescue.md | 7 +++++++ demo/api/block-meta.json | 2 +- .../admin/blocks-admin/src/blocks/YouTubeVideoBlock.tsx | 4 +++- packages/api/blocks-api/block-meta.json | 4 ++-- packages/api/blocks-api/src/blocks/youtube-video.block.ts | 4 ++-- packages/api/cms-api/block-meta.json | 4 ++-- packages/site/cms-site/src/blocks/YouTubeVideoBlock.tsx | 2 ++ 7 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 .changeset/real-plants-rescue.md diff --git a/.changeset/real-plants-rescue.md b/.changeset/real-plants-rescue.md new file mode 100644 index 0000000000..9f21a69e85 --- /dev/null +++ b/.changeset/real-plants-rescue.md @@ -0,0 +1,7 @@ +--- +"@comet/blocks-api": patch +--- + +Fix type of `youtubeIdentifier` in `YouTubeVideoBlock` + +Previously, it was incorrectly typed as required. Now it's optional. diff --git a/demo/api/block-meta.json b/demo/api/block-meta.json index 077004ce90..b9689ee078 100644 --- a/demo/api/block-meta.json +++ b/demo/api/block-meta.json @@ -2061,7 +2061,7 @@ { "name": "youtubeIdentifier", "kind": "String", - "nullable": false + "nullable": true }, { "name": "aspectRatio", diff --git a/packages/admin/blocks-admin/src/blocks/YouTubeVideoBlock.tsx b/packages/admin/blocks-admin/src/blocks/YouTubeVideoBlock.tsx index 301448e0e9..1ab2889ce8 100644 --- a/packages/admin/blocks-admin/src/blocks/YouTubeVideoBlock.tsx +++ b/packages/admin/blocks-admin/src/blocks/YouTubeVideoBlock.tsx @@ -23,6 +23,8 @@ const isValidYouTubeIdentifier = (value: string) => { }; const validateIdentifier = (value?: string) => { + if (!value) return undefined; + return value && isValidYouTubeIdentifier(value) ? undefined : ( ); @@ -56,7 +58,7 @@ export const YouTubeVideoBlock: BlockInterface { - updateState({ ...state, ...newState }); + updateState(newState); }} initialValues={state} > diff --git a/packages/api/blocks-api/block-meta.json b/packages/api/blocks-api/block-meta.json index 0766981783..cc8f39e262 100644 --- a/packages/api/blocks-api/block-meta.json +++ b/packages/api/blocks-api/block-meta.json @@ -49,7 +49,7 @@ { "name": "youtubeIdentifier", "kind": "String", - "nullable": false + "nullable": true }, { "name": "aspectRatio", @@ -80,7 +80,7 @@ { "name": "youtubeIdentifier", "kind": "String", - "nullable": false + "nullable": true }, { "name": "aspectRatio", diff --git a/packages/api/blocks-api/src/blocks/youtube-video.block.ts b/packages/api/blocks-api/src/blocks/youtube-video.block.ts index 33938e621f..5f29cd1271 100644 --- a/packages/api/blocks-api/src/blocks/youtube-video.block.ts +++ b/packages/api/blocks-api/src/blocks/youtube-video.block.ts @@ -9,7 +9,7 @@ enum AspectRatio { } class YouTubeVideoBlockData extends BlockData { - @BlockField() + @BlockField({ nullable: true }) youtubeIdentifier?: string; @BlockField({ type: "enum", enum: AspectRatio }) @@ -28,7 +28,7 @@ class YouTubeVideoBlockData extends BlockData { class YouTubeVideoBlockInput extends BlockInput { @IsOptional() @IsString() - @BlockField() + @BlockField({ nullable: true }) youtubeIdentifier?: string; @IsEnum(AspectRatio) diff --git a/packages/api/cms-api/block-meta.json b/packages/api/cms-api/block-meta.json index 32cf18a691..5812f16f97 100644 --- a/packages/api/cms-api/block-meta.json +++ b/packages/api/cms-api/block-meta.json @@ -976,7 +976,7 @@ { "name": "youtubeIdentifier", "kind": "String", - "nullable": false + "nullable": true }, { "name": "aspectRatio", @@ -1007,7 +1007,7 @@ { "name": "youtubeIdentifier", "kind": "String", - "nullable": false + "nullable": true }, { "name": "aspectRatio", diff --git a/packages/site/cms-site/src/blocks/YouTubeVideoBlock.tsx b/packages/site/cms-site/src/blocks/YouTubeVideoBlock.tsx index b580ee56ef..daabe22a79 100644 --- a/packages/site/cms-site/src/blocks/YouTubeVideoBlock.tsx +++ b/packages/site/cms-site/src/blocks/YouTubeVideoBlock.tsx @@ -3,6 +3,7 @@ import styled from "styled-components"; import { YouTubeVideoBlockData } from "../blocks.generated"; import { withPreview } from "../iframebridge/withPreview"; +import { PreviewSkeleton } from "../previewskeleton/PreviewSkeleton"; import { PropsWithData } from "./PropsWithData"; interface VideoContainerProps { @@ -47,6 +48,7 @@ const parseYoutubeIdentifier = (value: string): string | undefined => { export const YouTubeVideoBlock = withPreview( ({ data: { youtubeIdentifier, autoplay, loop, showControls, aspectRatio } }: PropsWithData) => { + if (!youtubeIdentifier) return ; const identifier = parseYoutubeIdentifier(youtubeIdentifier); const searchParams = new URLSearchParams(); From 53ee875ebe780317c26e108b8b8c2e4c43cbdf19 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 31 May 2024 10:30:15 +0200 Subject: [PATCH 07/11] Version Packages (#2084) This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @comet/admin@6.11.0 ### Minor Changes - 8e3dec523: Change `writeClipboardText`/`readClipboardText` clipboard fallback to in-memory Using the local storage as a fallback caused issues when writing clipboard contents larger than 5MB. Changing the fallback to in-memory resolves the issue. ### Patch Changes - @comet/admin-icons@6.11.0 ## @comet/cms-admin@6.11.0 ### Minor Changes - e10753b65: Allow disabling the "Open preview" button in the `PageTree` for certain document types The "Open preview" button is shown for all document types in the `PageTree`. But some document types (e.g., links) don't have a preview. Clicking on the preview button leads to an error page. Now, it's possible to disable the button by setting `hasNoSitePreview` for the document: ```diff export const Link: DocumentInterface, GQLLinkInput> = { // ... + hasNoSitePreview: true, }; ``` - fdf9fa7cb: Automatic Redirects are now set to false if the page is unpublished or archived ### Patch Changes - 815ba51e7: Fix link target validation in `ExternalLinkBlock` Previously, two different validation checks were used. This resulted in an error when saving an invalid link target but no error message was shown. - Updated dependencies [8e3dec523] - @comet/admin@6.11.0 - @comet/admin-date-time@6.11.0 - @comet/admin-icons@6.11.0 - @comet/admin-rte@6.11.0 - @comet/admin-theme@6.11.0 - @comet/blocks-admin@6.11.0 ## @comet/cms-api@6.11.0 ### Minor Changes - 0db10a5f8: Add a console script to import redirects from a csv file You can use the script like this: `npm run console import-redirects file-to-import.csv` The CSV file must look like this: ```csv source;target;target_type;comment;scope_domain /test-source;/test-target;internal;Internal Example;main /test-source-external;https://www.comet-dxp.com/;external;External Example;secondary ``` ### Patch Changes - Updated dependencies [93a84b651] - @comet/blocks-api@6.11.0 ## @comet/admin-color-picker@6.11.0 ### Patch Changes - Updated dependencies [8e3dec523] - @comet/admin@6.11.0 - @comet/admin-icons@6.11.0 ## @comet/admin-date-time@6.11.0 ### Patch Changes - Updated dependencies [8e3dec523] - @comet/admin@6.11.0 - @comet/admin-icons@6.11.0 ## @comet/admin-react-select@6.11.0 ### Patch Changes - Updated dependencies [8e3dec523] - @comet/admin@6.11.0 ## @comet/admin-rte@6.11.0 ### Patch Changes - Updated dependencies [8e3dec523] - @comet/admin@6.11.0 - @comet/admin-icons@6.11.0 ## @comet/admin-theme@6.11.0 ### Patch Changes - @comet/admin-icons@6.11.0 ## @comet/blocks-admin@6.11.0 ### Patch Changes - Updated dependencies [8e3dec523] - @comet/admin@6.11.0 - @comet/admin-icons@6.11.0 ## @comet/blocks-api@6.11.0 ### Patch Changes - 93a84b651: Fix type of `youtubeIdentifier` in `YouTubeVideoBlock` Previously, it was incorrectly typed as required. Now it's optional. ## @comet/eslint-config@6.11.0 ### Patch Changes - @comet/eslint-plugin@6.11.0 ## @comet/admin-babel-preset@6.11.0 ## @comet/admin-icons@6.11.0 ## @comet/cli@6.11.0 ## @comet/eslint-plugin@6.11.0 ## @comet/cms-site@6.11.0 Co-authored-by: github-actions[bot] --- .changeset/few-actors-impress.md | 18 ---- .changeset/few-roses-worry.md | 5 -- .changeset/plenty-pillows-jog.md | 8 -- .changeset/real-plants-rescue.md | 7 -- .changeset/six-meals-lie.md | 8 -- .changeset/young-hotels-sparkle.md | 15 ---- .../admin/admin-babel-preset/CHANGELOG.md | 2 + .../admin/admin-babel-preset/package.json | 2 +- .../admin/admin-color-picker/CHANGELOG.md | 8 ++ .../admin/admin-color-picker/package.json | 10 +-- packages/admin/admin-date-time/CHANGELOG.md | 8 ++ packages/admin/admin-date-time/package.json | 10 +-- packages/admin/admin-icons/CHANGELOG.md | 2 + packages/admin/admin-icons/package.json | 6 +- .../admin/admin-react-select/CHANGELOG.md | 7 ++ .../admin/admin-react-select/package.json | 8 +- packages/admin/admin-rte/CHANGELOG.md | 8 ++ packages/admin/admin-rte/package.json | 10 +-- packages/admin/admin-theme/CHANGELOG.md | 6 ++ packages/admin/admin-theme/package.json | 8 +- packages/admin/admin/CHANGELOG.md | 13 +++ packages/admin/admin/package.json | 8 +- packages/admin/blocks-admin/CHANGELOG.md | 8 ++ packages/admin/blocks-admin/package.json | 12 +-- packages/admin/cms-admin/CHANGELOG.md | 36 ++++++++ packages/admin/cms-admin/package.json | 20 ++--- packages/api/blocks-api/CHANGELOG.md | 8 ++ packages/api/blocks-api/package.json | 4 +- packages/api/cms-api/CHANGELOG.md | 21 +++++ packages/api/cms-api/package.json | 6 +- packages/cli/CHANGELOG.md | 2 + packages/cli/package.json | 4 +- packages/eslint-config/CHANGELOG.md | 6 ++ packages/eslint-config/package.json | 4 +- packages/eslint-plugin/CHANGELOG.md | 2 + packages/eslint-plugin/package.json | 2 +- packages/site/cms-site/CHANGELOG.md | 2 + packages/site/cms-site/package.json | 6 +- pnpm-lock.yaml | 88 +++++++++---------- 39 files changed, 243 insertions(+), 165 deletions(-) delete mode 100644 .changeset/few-actors-impress.md delete mode 100644 .changeset/few-roses-worry.md delete mode 100644 .changeset/plenty-pillows-jog.md delete mode 100644 .changeset/real-plants-rescue.md delete mode 100644 .changeset/six-meals-lie.md delete mode 100644 .changeset/young-hotels-sparkle.md diff --git a/.changeset/few-actors-impress.md b/.changeset/few-actors-impress.md deleted file mode 100644 index 08d2173463..0000000000 --- a/.changeset/few-actors-impress.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@comet/cms-admin": minor ---- - -Allow disabling the "Open preview" button in the `PageTree` for certain document types - -The "Open preview" button is shown for all document types in the `PageTree`. -But some document types (e.g., links) don't have a preview. -Clicking on the preview button leads to an error page. - -Now, it's possible to disable the button by setting `hasNoSitePreview` for the document: - -```diff -export const Link: DocumentInterface, GQLLinkInput> = { - // ... -+ hasNoSitePreview: true, -}; -``` diff --git a/.changeset/few-roses-worry.md b/.changeset/few-roses-worry.md deleted file mode 100644 index e168ffa93d..0000000000 --- a/.changeset/few-roses-worry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@comet/cms-admin": minor ---- - -Automatic Redirects are now set to false if the page is unpublished or archived diff --git a/.changeset/plenty-pillows-jog.md b/.changeset/plenty-pillows-jog.md deleted file mode 100644 index 4484348f0b..0000000000 --- a/.changeset/plenty-pillows-jog.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@comet/admin": minor ---- - -Change `writeClipboardText`/`readClipboardText` clipboard fallback to in-memory - -Using the local storage as a fallback caused issues when writing clipboard contents larger than 5MB. -Changing the fallback to in-memory resolves the issue. diff --git a/.changeset/real-plants-rescue.md b/.changeset/real-plants-rescue.md deleted file mode 100644 index 9f21a69e85..0000000000 --- a/.changeset/real-plants-rescue.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@comet/blocks-api": patch ---- - -Fix type of `youtubeIdentifier` in `YouTubeVideoBlock` - -Previously, it was incorrectly typed as required. Now it's optional. diff --git a/.changeset/six-meals-lie.md b/.changeset/six-meals-lie.md deleted file mode 100644 index 69f6b4c99e..0000000000 --- a/.changeset/six-meals-lie.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@comet/cms-admin": patch ---- - -Fix link target validation in `ExternalLinkBlock` - -Previously, two different validation checks were used. -This resulted in an error when saving an invalid link target but no error message was shown. diff --git a/.changeset/young-hotels-sparkle.md b/.changeset/young-hotels-sparkle.md deleted file mode 100644 index 04ef4a4945..0000000000 --- a/.changeset/young-hotels-sparkle.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@comet/cms-api": minor ---- - -Add a console script to import redirects from a csv file - -You can use the script like this: `npm run console import-redirects file-to-import.csv` - -The CSV file must look like this: - -```csv -source;target;target_type;comment;scope_domain -/test-source;/test-target;internal;Internal Example;main -/test-source-external;https://www.comet-dxp.com/;external;External Example;secondary -``` diff --git a/packages/admin/admin-babel-preset/CHANGELOG.md b/packages/admin/admin-babel-preset/CHANGELOG.md index 1a5b09de9d..46d16f4d0a 100644 --- a/packages/admin/admin-babel-preset/CHANGELOG.md +++ b/packages/admin/admin-babel-preset/CHANGELOG.md @@ -1,5 +1,7 @@ # @comet/admin-babel-preset +## 6.11.0 + ## 6.10.0 ## 6.9.0 diff --git a/packages/admin/admin-babel-preset/package.json b/packages/admin/admin-babel-preset/package.json index 50a727e48a..b93bee3097 100644 --- a/packages/admin/admin-babel-preset/package.json +++ b/packages/admin/admin-babel-preset/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin-babel-preset", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", diff --git a/packages/admin/admin-color-picker/CHANGELOG.md b/packages/admin/admin-color-picker/CHANGELOG.md index d666666bdf..775111de55 100644 --- a/packages/admin/admin-color-picker/CHANGELOG.md +++ b/packages/admin/admin-color-picker/CHANGELOG.md @@ -1,5 +1,13 @@ # @comet/admin-color-picker +## 6.11.0 + +### Patch Changes + +- Updated dependencies [8e3dec523] + - @comet/admin@6.11.0 + - @comet/admin-icons@6.11.0 + ## 6.10.0 ### Patch Changes diff --git a/packages/admin/admin-color-picker/package.json b/packages/admin/admin-color-picker/package.json index d128bfdf5b..7df19f8287 100644 --- a/packages/admin/admin-color-picker/package.json +++ b/packages/admin/admin-color-picker/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin-color-picker", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -25,8 +25,8 @@ "start:types": "tsc --project ./tsconfig.json --emitDeclarationOnly --watch --preserveWatchOutput" }, "dependencies": { - "@comet/admin": "workspace:^6.10.0", - "@comet/admin-icons": "workspace:^6.10.0", + "@comet/admin": "workspace:^6.11.0", + "@comet/admin-icons": "workspace:^6.11.0", "clsx": "^1.1.1", "react-colorful": "^5.5.1", "tinycolor2": "^1.4.1", @@ -35,8 +35,8 @@ "devDependencies": { "@babel/cli": "^7.17.6", "@babel/core": "^7.20.12", - "@comet/admin-babel-preset": "workspace:^6.10.0", - "@comet/eslint-config": "workspace:^6.10.0", + "@comet/admin-babel-preset": "workspace:^6.11.0", + "@comet/eslint-config": "workspace:^6.11.0", "@mui/icons-material": "^5.0.0", "@mui/material": "^5.0.0", "@mui/styles": "^5.0.0", diff --git a/packages/admin/admin-date-time/CHANGELOG.md b/packages/admin/admin-date-time/CHANGELOG.md index 0632d1a257..69f94fc2d1 100644 --- a/packages/admin/admin-date-time/CHANGELOG.md +++ b/packages/admin/admin-date-time/CHANGELOG.md @@ -1,5 +1,13 @@ # @comet/admin-date-time +## 6.11.0 + +### Patch Changes + +- Updated dependencies [8e3dec523] + - @comet/admin@6.11.0 + - @comet/admin-icons@6.11.0 + ## 6.10.0 ### Patch Changes diff --git a/packages/admin/admin-date-time/package.json b/packages/admin/admin-date-time/package.json index af825f036d..8397a26471 100644 --- a/packages/admin/admin-date-time/package.json +++ b/packages/admin/admin-date-time/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin-date-time", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -25,8 +25,8 @@ "start:types": "tsc --project ./tsconfig.json --emitDeclarationOnly --watch --preserveWatchOutput" }, "dependencies": { - "@comet/admin": "workspace:^6.10.0", - "@comet/admin-icons": "workspace:^6.10.0", + "@comet/admin": "workspace:^6.11.0", + "@comet/admin-icons": "workspace:^6.11.0", "@mui/utils": "^5.4.1", "clsx": "^1.1.1", "date-fns": "^2.28.0", @@ -35,8 +35,8 @@ "devDependencies": { "@babel/cli": "^7.17.6", "@babel/core": "^7.20.12", - "@comet/admin-babel-preset": "workspace:^6.10.0", - "@comet/eslint-config": "workspace:^6.10.0", + "@comet/admin-babel-preset": "workspace:^6.11.0", + "@comet/eslint-config": "workspace:^6.11.0", "@mui/material": "^5.0.0", "@mui/styles": "^5.0.0", "@types/react": "^17.0", diff --git a/packages/admin/admin-icons/CHANGELOG.md b/packages/admin/admin-icons/CHANGELOG.md index b43a03529e..d10f42176d 100644 --- a/packages/admin/admin-icons/CHANGELOG.md +++ b/packages/admin/admin-icons/CHANGELOG.md @@ -1,5 +1,7 @@ # @comet/admin-icons +## 6.11.0 + ## 6.10.0 ## 6.9.0 diff --git a/packages/admin/admin-icons/package.json b/packages/admin/admin-icons/package.json index 5e57c24a69..26dba3c8a5 100644 --- a/packages/admin/admin-icons/package.json +++ b/packages/admin/admin-icons/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin-icons", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -24,8 +24,8 @@ "devDependencies": { "@babel/cli": "^7.17.6", "@babel/core": "^7.20.12", - "@comet/admin-babel-preset": "workspace:^6.10.0", - "@comet/eslint-config": "workspace:^6.10.0", + "@comet/admin-babel-preset": "workspace:^6.11.0", + "@comet/eslint-config": "workspace:^6.11.0", "@mui/material": "^5.0.0", "@types/cli-progress": "^3.8.0", "@types/node": "^18.0.0", diff --git a/packages/admin/admin-react-select/CHANGELOG.md b/packages/admin/admin-react-select/CHANGELOG.md index adfe0c32a4..c3881d05e6 100644 --- a/packages/admin/admin-react-select/CHANGELOG.md +++ b/packages/admin/admin-react-select/CHANGELOG.md @@ -1,5 +1,12 @@ # @comet/admin-react-select +## 6.11.0 + +### Patch Changes + +- Updated dependencies [8e3dec523] + - @comet/admin@6.11.0 + ## 6.10.0 ### Patch Changes diff --git a/packages/admin/admin-react-select/package.json b/packages/admin/admin-react-select/package.json index bb3385fc26..7770af56e2 100644 --- a/packages/admin/admin-react-select/package.json +++ b/packages/admin/admin-react-select/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin-react-select", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -25,14 +25,14 @@ "start:types": "tsc --project ./tsconfig.json --emitDeclarationOnly --watch --preserveWatchOutput" }, "dependencies": { - "@comet/admin": "workspace:^6.10.0", + "@comet/admin": "workspace:^6.11.0", "classnames": "^2.2.6" }, "devDependencies": { "@babel/cli": "^7.17.6", "@babel/core": "^7.20.12", - "@comet/admin-babel-preset": "workspace:^6.10.0", - "@comet/eslint-config": "workspace:^6.10.0", + "@comet/admin-babel-preset": "workspace:^6.11.0", + "@comet/eslint-config": "workspace:^6.11.0", "@mui/icons-material": "^5.0.0", "@mui/material": "^5.0.0", "@mui/styles": "^5.0.0", diff --git a/packages/admin/admin-rte/CHANGELOG.md b/packages/admin/admin-rte/CHANGELOG.md index 436ad24345..6a4c9d5211 100644 --- a/packages/admin/admin-rte/CHANGELOG.md +++ b/packages/admin/admin-rte/CHANGELOG.md @@ -1,5 +1,13 @@ # @comet/admin-rte +## 6.11.0 + +### Patch Changes + +- Updated dependencies [8e3dec523] + - @comet/admin@6.11.0 + - @comet/admin-icons@6.11.0 + ## 6.10.0 ### Patch Changes diff --git a/packages/admin/admin-rte/package.json b/packages/admin/admin-rte/package.json index b6b3e67809..3f49180a82 100644 --- a/packages/admin/admin-rte/package.json +++ b/packages/admin/admin-rte/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin-rte", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -27,8 +27,8 @@ "test:watch": "jest --watch" }, "dependencies": { - "@comet/admin": "workspace:^6.10.0", - "@comet/admin-icons": "workspace:^6.10.0", + "@comet/admin": "workspace:^6.11.0", + "@comet/admin-icons": "workspace:^6.11.0", "detect-browser": "^5.2.1", "draft-js-export-html": "^1.4.1", "draft-js-import-html": "^1.4.1", @@ -38,8 +38,8 @@ "devDependencies": { "@babel/cli": "^7.17.6", "@babel/core": "^7.20.12", - "@comet/admin-babel-preset": "workspace:^6.10.0", - "@comet/eslint-config": "workspace:^6.10.0", + "@comet/admin-babel-preset": "workspace:^6.11.0", + "@comet/eslint-config": "workspace:^6.11.0", "@mui/icons-material": "^5.0.0", "@mui/material": "^5.0.0", "@mui/styles": "^5.0.0", diff --git a/packages/admin/admin-theme/CHANGELOG.md b/packages/admin/admin-theme/CHANGELOG.md index f24e9e2483..25184b3463 100644 --- a/packages/admin/admin-theme/CHANGELOG.md +++ b/packages/admin/admin-theme/CHANGELOG.md @@ -1,5 +1,11 @@ # @comet/admin-theme +## 6.11.0 + +### Patch Changes + +- @comet/admin-icons@6.11.0 + ## 6.10.0 ### Patch Changes diff --git a/packages/admin/admin-theme/package.json b/packages/admin/admin-theme/package.json index c749caaf82..624592d3bf 100644 --- a/packages/admin/admin-theme/package.json +++ b/packages/admin/admin-theme/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin-theme", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -25,14 +25,14 @@ "start:types": "tsc --project ./tsconfig.json --emitDeclarationOnly --watch --preserveWatchOutput" }, "dependencies": { - "@comet/admin-icons": "workspace:^6.10.0", + "@comet/admin-icons": "workspace:^6.11.0", "@mui/utils": "^5.4.1" }, "devDependencies": { "@babel/cli": "^7.17.6", "@babel/core": "^7.20.12", - "@comet/admin-babel-preset": "workspace:^6.10.0", - "@comet/eslint-config": "workspace:^6.10.0", + "@comet/admin-babel-preset": "workspace:^6.11.0", + "@comet/eslint-config": "workspace:^6.11.0", "@mui/material": "^5.0.0", "@mui/styles": "^5.0.0", "@mui/system": "^5.0.0", diff --git a/packages/admin/admin/CHANGELOG.md b/packages/admin/admin/CHANGELOG.md index 3f9a016388..e2f32a7021 100644 --- a/packages/admin/admin/CHANGELOG.md +++ b/packages/admin/admin/CHANGELOG.md @@ -1,5 +1,18 @@ # @comet/admin +## 6.11.0 + +### Minor Changes + +- 8e3dec523: Change `writeClipboardText`/`readClipboardText` clipboard fallback to in-memory + + Using the local storage as a fallback caused issues when writing clipboard contents larger than 5MB. + Changing the fallback to in-memory resolves the issue. + +### Patch Changes + +- @comet/admin-icons@6.11.0 + ## 6.10.0 ### Minor Changes diff --git a/packages/admin/admin/package.json b/packages/admin/admin/package.json index 330da3cbca..f4f2a62403 100644 --- a/packages/admin/admin/package.json +++ b/packages/admin/admin/package.json @@ -1,6 +1,6 @@ { "name": "@comet/admin", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -27,7 +27,7 @@ "test:watch": "jest --watch" }, "dependencies": { - "@comet/admin-icons": "workspace:^6.10.0", + "@comet/admin-icons": "workspace:^6.11.0", "@mui/private-theming": "^5.0.0", "clsx": "^1.1.1", "exceljs": "^3.4.0", @@ -46,8 +46,8 @@ "@apollo/client": "^3.7.0", "@babel/cli": "^7.17.6", "@babel/core": "^7.20.12", - "@comet/admin-babel-preset": "workspace:^6.10.0", - "@comet/eslint-config": "workspace:^6.10.0", + "@comet/admin-babel-preset": "workspace:^6.11.0", + "@comet/eslint-config": "workspace:^6.11.0", "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", "@mui/icons-material": "^5.0.0", diff --git a/packages/admin/blocks-admin/CHANGELOG.md b/packages/admin/blocks-admin/CHANGELOG.md index 296d2f72ce..501823a7a6 100644 --- a/packages/admin/blocks-admin/CHANGELOG.md +++ b/packages/admin/blocks-admin/CHANGELOG.md @@ -1,5 +1,13 @@ # @comet/blocks-admin +## 6.11.0 + +### Patch Changes + +- Updated dependencies [8e3dec523] + - @comet/admin@6.11.0 + - @comet/admin-icons@6.11.0 + ## 6.10.0 ### Patch Changes diff --git a/packages/admin/blocks-admin/package.json b/packages/admin/blocks-admin/package.json index c0565956a6..594efcb35b 100644 --- a/packages/admin/blocks-admin/package.json +++ b/packages/admin/blocks-admin/package.json @@ -1,6 +1,6 @@ { "name": "@comet/blocks-admin", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -29,8 +29,8 @@ "test:watch": "jest --watch" }, "dependencies": { - "@comet/admin": "workspace:^6.10.0", - "@comet/admin-icons": "workspace:^6.10.0", + "@comet/admin": "workspace:^6.11.0", + "@comet/admin-icons": "workspace:^6.11.0", "@mui/lab": "^5.0.0-alpha.76", "clipboard-copy": "^4.0.0", "clsx": "^1.1.1", @@ -42,9 +42,9 @@ "devDependencies": { "@babel/cli": "^7.17.6", "@babel/core": "^7.20.12", - "@comet/admin-babel-preset": "workspace:^6.10.0", - "@comet/cli": "workspace:^6.10.0", - "@comet/eslint-config": "workspace:^6.10.0", + "@comet/admin-babel-preset": "workspace:^6.11.0", + "@comet/cli": "workspace:^6.11.0", + "@comet/eslint-config": "workspace:^6.11.0", "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", "@mui/lab": "^5.0.0-alpha.76", diff --git a/packages/admin/cms-admin/CHANGELOG.md b/packages/admin/cms-admin/CHANGELOG.md index 4bf1f77ac2..0c4e9d97c1 100644 --- a/packages/admin/cms-admin/CHANGELOG.md +++ b/packages/admin/cms-admin/CHANGELOG.md @@ -1,5 +1,41 @@ # @comet/cms-admin +## 6.11.0 + +### Minor Changes + +- e10753b65: Allow disabling the "Open preview" button in the `PageTree` for certain document types + + The "Open preview" button is shown for all document types in the `PageTree`. + But some document types (e.g., links) don't have a preview. + Clicking on the preview button leads to an error page. + + Now, it's possible to disable the button by setting `hasNoSitePreview` for the document: + + ```diff + export const Link: DocumentInterface, GQLLinkInput> = { + // ... + + hasNoSitePreview: true, + }; + ``` + +- fdf9fa7cb: Automatic Redirects are now set to false if the page is unpublished or archived + +### Patch Changes + +- 815ba51e7: Fix link target validation in `ExternalLinkBlock` + + Previously, two different validation checks were used. + This resulted in an error when saving an invalid link target but no error message was shown. + +- Updated dependencies [8e3dec523] + - @comet/admin@6.11.0 + - @comet/admin-date-time@6.11.0 + - @comet/admin-icons@6.11.0 + - @comet/admin-rte@6.11.0 + - @comet/admin-theme@6.11.0 + - @comet/blocks-admin@6.11.0 + ## 6.10.0 ### Minor Changes diff --git a/packages/admin/cms-admin/package.json b/packages/admin/cms-admin/package.json index 4974d1a221..9a8dd5dfe9 100644 --- a/packages/admin/cms-admin/package.json +++ b/packages/admin/cms-admin/package.json @@ -1,6 +1,6 @@ { "name": "@comet/cms-admin", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -34,12 +34,12 @@ "test:watch": "jest --watch" }, "dependencies": { - "@comet/admin": "workspace:^6.10.0", - "@comet/admin-date-time": "workspace:^6.10.0", - "@comet/admin-icons": "workspace:^6.10.0", - "@comet/admin-rte": "workspace:^6.10.0", - "@comet/admin-theme": "workspace:^6.10.0", - "@comet/blocks-admin": "workspace:^6.10.0", + "@comet/admin": "workspace:^6.11.0", + "@comet/admin-date-time": "workspace:^6.11.0", + "@comet/admin-icons": "workspace:^6.11.0", + "@comet/admin-rte": "workspace:^6.11.0", + "@comet/admin-theme": "workspace:^6.11.0", + "@comet/blocks-admin": "workspace:^6.11.0", "@graphql-tools/graphql-file-loader": "^7.5.17", "@graphql-tools/load": "^7.8.14", "@graphql-typed-document-node/core": "^3.1.1", @@ -80,9 +80,9 @@ "@apollo/client": "^3.7.0", "@babel/cli": "^7.17.6", "@babel/core": "^7.20.12", - "@comet/admin-babel-preset": "workspace:^6.10.0", - "@comet/cli": "workspace:^6.10.0", - "@comet/eslint-config": "workspace:^6.10.0", + "@comet/admin-babel-preset": "workspace:^6.11.0", + "@comet/cli": "workspace:^6.11.0", + "@comet/eslint-config": "workspace:^6.11.0", "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", "@graphql-codegen/cli": "^2.0.0", diff --git a/packages/api/blocks-api/CHANGELOG.md b/packages/api/blocks-api/CHANGELOG.md index 3850267025..31bd7533e8 100644 --- a/packages/api/blocks-api/CHANGELOG.md +++ b/packages/api/blocks-api/CHANGELOG.md @@ -1,5 +1,13 @@ # @comet/blocks-api +## 6.11.0 + +### Patch Changes + +- 93a84b651: Fix type of `youtubeIdentifier` in `YouTubeVideoBlock` + + Previously, it was incorrectly typed as required. Now it's optional. + ## 6.10.0 ## 6.9.0 diff --git a/packages/api/blocks-api/package.json b/packages/api/blocks-api/package.json index 96deb47963..10b5219bb8 100644 --- a/packages/api/blocks-api/package.json +++ b/packages/api/blocks-api/package.json @@ -1,6 +1,6 @@ { "name": "@comet/blocks-api", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -28,7 +28,7 @@ "rimraf": "^3.0.0" }, "devDependencies": { - "@comet/eslint-config": "workspace:^6.10.0", + "@comet/eslint-config": "workspace:^6.11.0", "@nestjs/common": "^9.0.0", "@types/draft-js": "^0.11.10", "@types/jest": "^29.5.0", diff --git a/packages/api/cms-api/CHANGELOG.md b/packages/api/cms-api/CHANGELOG.md index 7cb8763670..e499f9420d 100644 --- a/packages/api/cms-api/CHANGELOG.md +++ b/packages/api/cms-api/CHANGELOG.md @@ -1,5 +1,26 @@ # @comet/cms-api +## 6.11.0 + +### Minor Changes + +- 0db10a5f8: Add a console script to import redirects from a csv file + + You can use the script like this: `npm run console import-redirects file-to-import.csv` + + The CSV file must look like this: + + ```csv + source;target;target_type;comment;scope_domain + /test-source;/test-target;internal;Internal Example;main + /test-source-external;https://www.comet-dxp.com/;external;External Example;secondary + ``` + +### Patch Changes + +- Updated dependencies [93a84b651] + - @comet/blocks-api@6.11.0 + ## 6.10.0 ### Minor Changes diff --git a/packages/api/cms-api/package.json b/packages/api/cms-api/package.json index 8881151d5e..9a0df232a8 100644 --- a/packages/api/cms-api/package.json +++ b/packages/api/cms-api/package.json @@ -1,6 +1,6 @@ { "name": "@comet/cms-api", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -31,7 +31,7 @@ "test:watch": "jest --watch" }, "dependencies": { - "@comet/blocks-api": "workspace:^6.10.0", + "@comet/blocks-api": "workspace:^6.11.0", "@fast-csv/parse": "^4.3.6", "@golevelup/nestjs-discovery": "^3.0.0", "@hapi/accept": "^5.0.2", @@ -80,7 +80,7 @@ "@aws-sdk/client-s3": "^3.47.0", "@aws-sdk/types": "^3.47.0", "@azure/storage-blob": "^12.0.0", - "@comet/eslint-config": "workspace:^6.10.0", + "@comet/eslint-config": "workspace:^6.11.0", "@golevelup/ts-jest": "^0.4.0", "@kubernetes/client-node": "^0.18.1", "@mikro-orm/cli": "^5.7.1", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index fc48a986fd..bdb521b932 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,7 @@ # @comet/cli +## 6.11.0 + ## 6.10.0 ## 6.9.0 diff --git a/packages/cli/package.json b/packages/cli/package.json index 4dcaf627b1..d94066eef3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@comet/cli", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -29,7 +29,7 @@ "prettier": "^2.7.1" }, "devDependencies": { - "@comet/eslint-config": "workspace:^6.10.0", + "@comet/eslint-config": "workspace:^6.11.0", "@types/node": "^18.0.0", "eslint": "^8.0.0", "npm-run-all": "^4.1.5", diff --git a/packages/eslint-config/CHANGELOG.md b/packages/eslint-config/CHANGELOG.md index fe252f2e2b..bd33305c82 100644 --- a/packages/eslint-config/CHANGELOG.md +++ b/packages/eslint-config/CHANGELOG.md @@ -1,5 +1,11 @@ # @comet/eslint-config +## 6.11.0 + +### Patch Changes + +- @comet/eslint-plugin@6.11.0 + ## 6.10.0 ### Patch Changes diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 6c9e14b159..c39baa7dc1 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@comet/eslint-config", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -23,7 +23,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-simple-import-sort": "^9.0.0", "eslint-plugin-unused-imports": "^2.0.0", - "@comet/eslint-plugin": "workspace:^6.10.0" + "@comet/eslint-plugin": "workspace:^6.11.0" }, "devDependencies": { "eslint": "^8.32.0", diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index c8339e4bd3..2060e2e4d4 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,5 +1,7 @@ # @comet/eslint-plugin +## 6.11.0 + ## 6.10.0 ## 6.9.0 diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index ec89872065..84e66260e7 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@comet/eslint-plugin", - "version": "6.10.0", + "version": "6.11.0", "main": "lib/index.js", "scripts": { "build": "$npm_execpath run clean && tsc", diff --git a/packages/site/cms-site/CHANGELOG.md b/packages/site/cms-site/CHANGELOG.md index 62f5eb6ede..59ada32752 100644 --- a/packages/site/cms-site/CHANGELOG.md +++ b/packages/site/cms-site/CHANGELOG.md @@ -1,5 +1,7 @@ # @comet/cms-site +## 6.11.0 + ## 6.10.0 ### Minor Changes diff --git a/packages/site/cms-site/package.json b/packages/site/cms-site/package.json index 523c0a3537..14330f4bfa 100644 --- a/packages/site/cms-site/package.json +++ b/packages/site/cms-site/package.json @@ -1,6 +1,6 @@ { "name": "@comet/cms-site", - "version": "6.10.0", + "version": "6.11.0", "repository": { "type": "git", "url": "https://github.com/vivid-planet/comet", @@ -32,8 +32,8 @@ "use-debounce": "^6.0.0" }, "devDependencies": { - "@comet/cli": "workspace:^6.10.0", - "@comet/eslint-config": "workspace:^6.10.0", + "@comet/cli": "workspace:^6.11.0", + "@comet/eslint-config": "workspace:^6.11.0", "@gitbeaker/node": "^34.0.0", "@testing-library/react-hooks": "^8.0.0", "@types/draft-js": "^0.11.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfe95017d6..f952f30426 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -958,7 +958,7 @@ importers: packages/admin/admin: dependencies: '@comet/admin-icons': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-icons '@mui/private-theming': specifier: ^5.0.0 @@ -1010,10 +1010,10 @@ importers: specifier: ^7.20.12 version: 7.20.12 '@comet/admin-babel-preset': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-babel-preset '@comet/eslint-config': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../eslint-config '@emotion/react': specifier: ^11.5.0 @@ -1181,10 +1181,10 @@ importers: packages/admin/admin-color-picker: dependencies: '@comet/admin': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin '@comet/admin-icons': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-icons clsx: specifier: ^1.1.1 @@ -1206,10 +1206,10 @@ importers: specifier: ^7.20.12 version: 7.20.12 '@comet/admin-babel-preset': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-babel-preset '@comet/eslint-config': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../eslint-config '@mui/icons-material': specifier: ^5.0.0 @@ -1263,10 +1263,10 @@ importers: packages/admin/admin-date-time: dependencies: '@comet/admin': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin '@comet/admin-icons': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-icons '@mui/utils': specifier: ^5.4.1 @@ -1288,10 +1288,10 @@ importers: specifier: ^7.20.12 version: 7.20.12 '@comet/admin-babel-preset': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-babel-preset '@comet/eslint-config': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../eslint-config '@mui/material': specifier: ^5.0.0 @@ -1348,10 +1348,10 @@ importers: specifier: ^7.20.12 version: 7.20.12 '@comet/admin-babel-preset': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-babel-preset '@comet/eslint-config': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../eslint-config '@mui/material': specifier: ^5.0.0 @@ -1405,7 +1405,7 @@ importers: packages/admin/admin-react-select: dependencies: '@comet/admin': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin classnames: specifier: ^2.2.6 @@ -1418,10 +1418,10 @@ importers: specifier: ^7.20.12 version: 7.20.12 '@comet/admin-babel-preset': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-babel-preset '@comet/eslint-config': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../eslint-config '@mui/icons-material': specifier: ^5.0.0 @@ -1475,10 +1475,10 @@ importers: packages/admin/admin-rte: dependencies: '@comet/admin': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin '@comet/admin-icons': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-icons detect-browser: specifier: ^5.2.1 @@ -1503,10 +1503,10 @@ importers: specifier: ^7.20.12 version: 7.20.12 '@comet/admin-babel-preset': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-babel-preset '@comet/eslint-config': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../eslint-config '@mui/icons-material': specifier: ^5.0.0 @@ -1590,7 +1590,7 @@ importers: packages/admin/admin-theme: dependencies: '@comet/admin-icons': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-icons '@mui/utils': specifier: ^5.4.1 @@ -1603,10 +1603,10 @@ importers: specifier: ^7.20.12 version: 7.20.12 '@comet/admin-babel-preset': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-babel-preset '@comet/eslint-config': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../eslint-config '@mui/material': specifier: ^5.0.0 @@ -1651,10 +1651,10 @@ importers: packages/admin/blocks-admin: dependencies: '@comet/admin': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin '@comet/admin-icons': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-icons '@mui/lab': specifier: ^5.0.0-alpha.76 @@ -1685,13 +1685,13 @@ importers: specifier: ^7.20.12 version: 7.20.12 '@comet/admin-babel-preset': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-babel-preset '@comet/cli': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../cli '@comet/eslint-config': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../eslint-config '@emotion/react': specifier: ^11.5.0 @@ -1784,22 +1784,22 @@ importers: packages/admin/cms-admin: dependencies: '@comet/admin': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin '@comet/admin-date-time': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-date-time '@comet/admin-icons': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-icons '@comet/admin-rte': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-rte '@comet/admin-theme': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-theme '@comet/blocks-admin': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../blocks-admin '@graphql-tools/graphql-file-loader': specifier: ^7.5.17 @@ -1917,13 +1917,13 @@ importers: specifier: ^7.20.12 version: 7.20.12 '@comet/admin-babel-preset': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../admin-babel-preset '@comet/cli': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../cli '@comet/eslint-config': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../eslint-config '@emotion/react': specifier: ^11.5.0 @@ -2101,7 +2101,7 @@ importers: version: 3.0.2 devDependencies: '@comet/eslint-config': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../eslint-config '@nestjs/common': specifier: ^9.0.0 @@ -2158,7 +2158,7 @@ importers: packages/api/cms-api: dependencies: '@comet/blocks-api': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../blocks-api '@fast-csv/parse': specifier: ^4.3.6 @@ -2300,7 +2300,7 @@ importers: specifier: ^12.0.0 version: 12.12.0 '@comet/eslint-config': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../eslint-config '@golevelup/ts-jest': specifier: ^0.4.0 @@ -2439,7 +2439,7 @@ importers: version: 2.8.3 devDependencies: '@comet/eslint-config': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../eslint-config '@types/node': specifier: ^18.0.0 @@ -2463,7 +2463,7 @@ importers: specifier: ^1.4.1 version: 1.4.1 '@comet/eslint-plugin': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../eslint-plugin '@next/eslint-plugin-next': specifier: ^12.0.0 @@ -2561,10 +2561,10 @@ importers: version: 6.0.1(react@17.0.2) devDependencies: '@comet/cli': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../cli '@comet/eslint-config': - specifier: workspace:^6.10.0 + specifier: workspace:^6.11.0 version: link:../../eslint-config '@gitbeaker/node': specifier: ^34.0.0 From dc7eaeccb4bd3a39c63c01ce6da21f37a0c104db Mon Sep 17 00:00:00 2001 From: David Schwarz <118435139+VP-DS@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:10:38 +0200 Subject: [PATCH 08/11] Hide translation button for `FinalFormSearchTextField` (#2125) Co-authored-by: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> --- .changeset/epic-treasure-search.md | 5 +++++ packages/admin/admin/src/form/FinalFormSearchTextField.tsx | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/epic-treasure-search.md diff --git a/.changeset/epic-treasure-search.md b/.changeset/epic-treasure-search.md new file mode 100644 index 0000000000..04b0141aeb --- /dev/null +++ b/.changeset/epic-treasure-search.md @@ -0,0 +1,5 @@ +--- +"@comet/admin-rte": patch +--- + +Hide translation button for `FinalFormSearchTextField` diff --git a/packages/admin/admin/src/form/FinalFormSearchTextField.tsx b/packages/admin/admin/src/form/FinalFormSearchTextField.tsx index 2a01b6c514..cf46283d8d 100644 --- a/packages/admin/admin/src/form/FinalFormSearchTextField.tsx +++ b/packages/admin/admin/src/form/FinalFormSearchTextField.tsx @@ -44,6 +44,7 @@ function SearchTextField({ endAdornment ) } + disableContentTranslation={true} /> ); } From 0597b1e0a93d24270cf62d974d99b3b1a77c5573 Mon Sep 17 00:00:00 2001 From: Franz Unger Date: Tue, 4 Jun 2024 15:43:11 +0200 Subject: [PATCH 09/11] Backport `DisablePermissionCheck` constant from `next` (#2129) See https://github.com/vivid-planet/comet/pull/1877 --------- Co-authored-by: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> Co-authored-by: Thomas Dax --- .changeset/wild-suns-eat.md | 7 +++++++ packages/api/cms-api/src/auth/resolver/auth.resolver.ts | 4 ++-- .../src/user-permissions/auth/user-permissions.guard.ts | 3 ++- .../decorators/required-permission.decorator.ts | 7 ++++++- .../src/user-permissions/user-permissions.service.ts | 3 ++- 5 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 .changeset/wild-suns-eat.md diff --git a/.changeset/wild-suns-eat.md b/.changeset/wild-suns-eat.md new file mode 100644 index 0000000000..dd1f1a723e --- /dev/null +++ b/.changeset/wild-suns-eat.md @@ -0,0 +1,7 @@ +--- +"@comet/cms-api": minor +--- + +Add `DisablePermissionCheck` constant for use in `@RequiredPermission` decorator + +You can disable authorization for a resolver or operation by adding the decorator `@RequiredPermission(DisablePermissionCheck)` diff --git a/packages/api/cms-api/src/auth/resolver/auth.resolver.ts b/packages/api/cms-api/src/auth/resolver/auth.resolver.ts index 333aee4bbf..83b6d11f1d 100644 --- a/packages/api/cms-api/src/auth/resolver/auth.resolver.ts +++ b/packages/api/cms-api/src/auth/resolver/auth.resolver.ts @@ -3,9 +3,9 @@ import { Context, Mutation, Query, Resolver } from "@nestjs/graphql"; import { IncomingMessage } from "http"; import { SkipBuild } from "../../builds/skip-build.decorator"; +import { DisablePermissionCheck, RequiredPermission } from "../../user-permissions/decorators/required-permission.decorator"; import { CurrentUser } from "../../user-permissions/dto/current-user"; import { GetCurrentUser } from "../decorators/get-current-user.decorator"; -import { PublicApi } from "../decorators/public-api.decorator"; interface AuthResolverConfig { currentUser?: Type; // TODO Remove in future version as it is not used and here for backwards compatibility @@ -15,7 +15,7 @@ interface AuthResolverConfig { export function createAuthResolver(config?: AuthResolverConfig): Type { @Resolver(() => CurrentUser) - @PublicApi() + @RequiredPermission(DisablePermissionCheck) class AuthResolver { @Query(() => CurrentUser) async currentUser(@GetCurrentUser() user: CurrentUser): Promise { diff --git a/packages/api/cms-api/src/user-permissions/auth/user-permissions.guard.ts b/packages/api/cms-api/src/user-permissions/auth/user-permissions.guard.ts index 1df6769c60..a860cac9d9 100644 --- a/packages/api/cms-api/src/user-permissions/auth/user-permissions.guard.ts +++ b/packages/api/cms-api/src/user-permissions/auth/user-permissions.guard.ts @@ -3,7 +3,7 @@ import { Reflector } from "@nestjs/core"; import { GqlContextType, GqlExecutionContext } from "@nestjs/graphql"; import { ContentScopeService } from "../content-scope.service"; -import { RequiredPermissionMetadata } from "../decorators/required-permission.decorator"; +import { DisablePermissionCheck, RequiredPermissionMetadata } from "../decorators/required-permission.decorator"; import { CurrentUser } from "../dto/current-user"; import { ACCESS_CONTROL_SERVICE } from "../user-permissions.constants"; import { AccessControlServiceInterface, SystemUser } from "../user-permissions.types"; @@ -32,6 +32,7 @@ export class UserPermissionsGuard implements CanActivate { if (!requiredPermission && this.isResolvingGraphQLField(context)) return true; if (!requiredPermission) throw new Error(`RequiredPermission decorator is missing in ${location}`); const requiredPermissions = requiredPermission.requiredPermission; + if (requiredPermissions.includes(DisablePermissionCheck)) return true; if (requiredPermissions.length === 0) throw new Error(`RequiredPermission decorator has empty permissions in ${location}`); if (this.isResolvingGraphQLField(context) || requiredPermission.options?.skipScopeCheck) { // At least one permission is required diff --git a/packages/api/cms-api/src/user-permissions/decorators/required-permission.decorator.ts b/packages/api/cms-api/src/user-permissions/decorators/required-permission.decorator.ts index 17fe656d24..1d9828068a 100644 --- a/packages/api/cms-api/src/user-permissions/decorators/required-permission.decorator.ts +++ b/packages/api/cms-api/src/user-permissions/decorators/required-permission.decorator.ts @@ -9,7 +9,12 @@ export type RequiredPermissionMetadata = { options: RequiredPermissionOptions | undefined; }; -export const RequiredPermission = (requiredPermission: string | string[], options?: RequiredPermissionOptions): CustomDecorator => { +export const DisablePermissionCheck = "disablePermissionCheck"; + +export const RequiredPermission = ( + requiredPermission: string | string[] | "disablePermissionCheck", + options?: RequiredPermissionOptions, +): CustomDecorator => { return SetMetadata("requiredPermission", { requiredPermission: Array.isArray(requiredPermission) ? requiredPermission : [requiredPermission], options, diff --git a/packages/api/cms-api/src/user-permissions/user-permissions.service.ts b/packages/api/cms-api/src/user-permissions/user-permissions.service.ts index 9c5ef80720..c76182131d 100644 --- a/packages/api/cms-api/src/user-permissions/user-permissions.service.ts +++ b/packages/api/cms-api/src/user-permissions/user-permissions.service.ts @@ -7,7 +7,7 @@ import { JwtPayload } from "jsonwebtoken"; import isEqual from "lodash.isequal"; import getUuid from "uuid-by-string"; -import { RequiredPermissionMetadata } from "./decorators/required-permission.decorator"; +import { DisablePermissionCheck, RequiredPermissionMetadata } from "./decorators/required-permission.decorator"; import { CurrentUser } from "./dto/current-user"; import { FindUsersArgs } from "./dto/paginated-user-list"; import { UserContentScopes } from "./entities/user-content-scopes.entity"; @@ -53,6 +53,7 @@ export class UserPermissionsService { ...(await this.discoveryService.controllersWithMetaAtKey("requiredPermission")), ] .flatMap((p) => p.meta.requiredPermission) + .filter((p) => p !== DisablePermissionCheck) .sort(), ), ]; From 3ee8c7a33bb3da30a9173ae01294e4d869582265 Mon Sep 17 00:00:00 2001 From: F10Ragger <103995707+F10Ragger@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:11:58 +0200 Subject: [PATCH 10/11] Add `DamFileDownloadLinkBlock` (#1228) Can be used to download a DAM file or open it in a new tab. --------- Co-authored-by: Florian Ragger Co-authored-by: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> --- .changeset/green-years-drum.md | 9 ++ demo/admin/src/common/blocks/LinkBlock.tsx | 9 +- demo/api/block-meta.json | 60 +++++++- .../src/common/blocks/linkBlock/link.block.ts | 6 +- demo/site/src/blocks/LinkBlock.tsx | 15 +- packages/admin/admin/src/messages.ts | 1 + .../dam/blocks/DamFileDownloadLinkBlock.tsx | 130 ++++++++++++++++++ packages/admin/cms-admin/src/index.ts | 1 + packages/api/cms-api/block-meta.json | 54 ++++++++ .../blocks/dam-file-download-link.block.ts | 128 +++++++++++++++++ .../cms-api/src/dam/files/files.controller.ts | 45 ++++++ .../cms-api/src/dam/files/files.service.ts | 22 +++ packages/api/cms-api/src/index.ts | 1 + .../src/blocks/DamFileDownloadLinkBlock.tsx | 33 +++++ packages/site/cms-site/src/index.ts | 1 + 15 files changed, 508 insertions(+), 7 deletions(-) create mode 100644 .changeset/green-years-drum.md create mode 100644 packages/admin/cms-admin/src/dam/blocks/DamFileDownloadLinkBlock.tsx create mode 100644 packages/api/cms-api/src/dam/blocks/dam-file-download-link.block.ts create mode 100644 packages/site/cms-site/src/blocks/DamFileDownloadLinkBlock.tsx diff --git a/.changeset/green-years-drum.md b/.changeset/green-years-drum.md new file mode 100644 index 0000000000..7073e9b514 --- /dev/null +++ b/.changeset/green-years-drum.md @@ -0,0 +1,9 @@ +--- +"@comet/cms-admin": minor +"@comet/cms-site": minor +"@comet/cms-api": minor +--- + +Add a `DamFileDownloadLinkBlock` that can be used to download a file or open it in a new tab + +Also, add new `/dam/files/download/:hash/:fileId/:filename` endpoint for downloading assets. diff --git a/demo/admin/src/common/blocks/LinkBlock.tsx b/demo/admin/src/common/blocks/LinkBlock.tsx index fdc1a68b92..c81fcff1a7 100644 --- a/demo/admin/src/common/blocks/LinkBlock.tsx +++ b/demo/admin/src/common/blocks/LinkBlock.tsx @@ -1,6 +1,11 @@ -import { createLinkBlock, ExternalLinkBlock, InternalLinkBlock } from "@comet/cms-admin"; +import { createLinkBlock, DamFileDownloadLinkBlock, ExternalLinkBlock, InternalLinkBlock } from "@comet/cms-admin"; import { NewsLinkBlock } from "@src/news/blocks/NewsLinkBlock"; export const LinkBlock = createLinkBlock({ - supportedBlocks: { internal: InternalLinkBlock, external: ExternalLinkBlock, news: NewsLinkBlock }, + supportedBlocks: { + internal: InternalLinkBlock, + external: ExternalLinkBlock, + news: NewsLinkBlock, + damFileDownload: DamFileDownloadLinkBlock, + }, }); diff --git a/demo/api/block-meta.json b/demo/api/block-meta.json index b9689ee078..225dacfe22 100644 --- a/demo/api/block-meta.json +++ b/demo/api/block-meta.json @@ -160,6 +160,60 @@ } ] }, + { + "name": "DamFileDownloadLink", + "fields": [ + { + "name": "file", + "kind": "NestedObject", + "object": { + "fields": [ + { + "name": "id", + "kind": "String", + "nullable": false + }, + { + "name": "name", + "kind": "String", + "nullable": false + }, + { + "name": "fileUrl", + "kind": "String", + "nullable": false + } + ] + }, + "nullable": true + }, + { + "name": "openFileType", + "kind": "Enum", + "enum": [ + "NewTab", + "Download" + ], + "nullable": false + } + ], + "inputFields": [ + { + "name": "fileId", + "kind": "String", + "nullable": true + }, + { + "name": "openFileType", + "kind": "Enum", + "enum": [ + "NewTab", + "Download" + ], + "nullable": false + } + ] + }, { "name": "DamImage", "fields": [ @@ -741,7 +795,8 @@ "blocks": { "internal": "InternalLink", "external": "ExternalLink", - "news": "NewsLink" + "news": "NewsLink", + "damFileDownload": "DamFileDownloadLink" }, "nullable": false } @@ -770,7 +825,8 @@ "blocks": { "internal": "InternalLink", "external": "ExternalLink", - "news": "NewsLink" + "news": "NewsLink", + "damFileDownload": "DamFileDownloadLink" }, "nullable": false } diff --git a/demo/api/src/common/blocks/linkBlock/link.block.ts b/demo/api/src/common/blocks/linkBlock/link.block.ts index b67dcc04fc..5df9b0eb2c 100644 --- a/demo/api/src/common/blocks/linkBlock/link.block.ts +++ b/demo/api/src/common/blocks/linkBlock/link.block.ts @@ -1,5 +1,7 @@ import { ExternalLinkBlock } from "@comet/blocks-api"; -import { createLinkBlock, InternalLinkBlock } from "@comet/cms-api"; +import { createLinkBlock, DamFileDownloadLinkBlock, InternalLinkBlock } from "@comet/cms-api"; import { NewsLinkBlock } from "@src/news/blocks/news-link.block"; -export const LinkBlock = createLinkBlock({ supportedBlocks: { internal: InternalLinkBlock, external: ExternalLinkBlock, news: NewsLinkBlock } }); +export const LinkBlock = createLinkBlock({ + supportedBlocks: { internal: InternalLinkBlock, external: ExternalLinkBlock, news: NewsLinkBlock, damFileDownload: DamFileDownloadLinkBlock }, +}); diff --git a/demo/site/src/blocks/LinkBlock.tsx b/demo/site/src/blocks/LinkBlock.tsx index 42478c5720..ba7a379d5b 100644 --- a/demo/site/src/blocks/LinkBlock.tsx +++ b/demo/site/src/blocks/LinkBlock.tsx @@ -1,4 +1,12 @@ -import { ExternalLinkBlock, InternalLinkBlock, OneOfBlock, PropsWithData, SupportedBlocks, withPreview } from "@comet/cms-site"; +import { + DamFileDownloadLinkBlock, + ExternalLinkBlock, + InternalLinkBlock, + OneOfBlock, + PropsWithData, + SupportedBlocks, + withPreview, +} from "@comet/cms-site"; import { LinkBlockData } from "@src/blocks.generated"; import { NewsLinkBlock } from "@src/news/blocks/NewsLinkBlock"; import * as React from "react"; @@ -19,6 +27,11 @@ const supportedBlocks: SupportedBlocks = { {children} ), + damFileDownload: ({ children, title, ...props }) => ( + + {children} + + ), }; interface LinkBlockProps extends PropsWithData { diff --git a/packages/admin/admin/src/messages.ts b/packages/admin/admin/src/messages.ts index bd2c4b9335..f594fe3490 100644 --- a/packages/admin/admin/src/messages.ts +++ b/packages/admin/admin/src/messages.ts @@ -51,4 +51,5 @@ export const messages = defineMessages({ filter: { id: "comet.generic.filter", defaultMessage: "Filter" }, copyUrl: { id: "comet.generic.copyUrl", defaultMessage: "Copy URL" }, deleteItem: { id: "comet.generic.deleteItem", defaultMessage: "Delete Item" }, + empty: { id: "comet.generic.empty", defaultMessage: "Empty" }, }); diff --git a/packages/admin/cms-admin/src/dam/blocks/DamFileDownloadLinkBlock.tsx b/packages/admin/cms-admin/src/dam/blocks/DamFileDownloadLinkBlock.tsx new file mode 100644 index 0000000000..3df04799ee --- /dev/null +++ b/packages/admin/cms-admin/src/dam/blocks/DamFileDownloadLinkBlock.tsx @@ -0,0 +1,130 @@ +import { gql } from "@apollo/client"; +import { Field, FinalFormSelect, messages } from "@comet/admin"; +import { Delete } from "@comet/admin-icons"; +import { AdminComponentButton, AdminComponentPaper, BlockCategory, BlockInterface, BlocksFinalForm, createBlockSkeleton } from "@comet/blocks-admin"; +import { Box, Divider, MenuItem, Typography } from "@mui/material"; +import * as React from "react"; +import { FormattedMessage } from "react-intl"; + +import { DamFileDownloadLinkBlockData, DamFileDownloadLinkBlockInput } from "../../blocks.generated"; +import { CmsBlockContext } from "../../blocks/CmsBlockContextProvider"; +import { DamPathLazy } from "../../form/file/DamPathLazy"; +import { FileField } from "../../form/file/FileField"; +import { GQLDamFileDownloadLinkFileQuery, GQLDamFileDownloadLinkFileQueryVariables } from "./DamFileDownloadLinkBlock.generated"; + +export const DamFileDownloadLinkBlock: BlockInterface = { + ...createBlockSkeleton(), + + name: "DamFileDownloadLink", + + displayName: , + + previewContent: (state) => (state.file ? [{ type: "text", content: state.file?.name }] : []), + + category: BlockCategory.Media, + + defaultValues: () => ({ + openFileType: "Download", + }), + + state2Output: (state) => ({ + fileId: state.file?.id ?? undefined, + openFileType: state.openFileType, + }), + + output2State: async (output, { apolloClient }: CmsBlockContext): Promise => { + const ret: DamFileDownloadLinkBlockData = { + openFileType: output.openFileType, + }; + + if (output.fileId === undefined) { + return ret; + } + + const { data } = await apolloClient.query({ + query: gql` + query DamFileDownloadLinkFile($id: ID!) { + damFile(id: $id) { + id + name + fileUrl + } + } + `, + variables: { id: output.fileId }, + }); + + const { damFile } = data; + + ret.file = { + id: damFile.id, + name: damFile.name, + fileUrl: damFile.fileUrl, + }; + + return ret; + }, + + definesOwnPadding: true, + + AdminComponent: ({ state, updateState }) => { + return ( + + onSubmit={(newValues) => { + updateState({ + file: newValues.file ?? undefined, + openFileType: newValues.openFileType, + }); + }} + initialValues={{ + file: state.file, + openFileType: state.openFileType ?? "Download", + }} + > + {state.file === undefined ? ( + + ) : ( + + + {state.file.name} + + + + + + } onClick={() => updateState({ ...state, file: undefined })}> + + + + )} + + + } + > + {(props) => ( + <> + + + + + + + + + + )} + + + + ); + }, +}; diff --git a/packages/admin/cms-admin/src/index.ts b/packages/admin/cms-admin/src/index.ts index a8cdccd2d6..512dd1d371 100644 --- a/packages/admin/cms-admin/src/index.ts +++ b/packages/admin/cms-admin/src/index.ts @@ -43,6 +43,7 @@ export type { ContentScopeConfigProps } from "./contentScope/useContentScopeConf export { useContentScopeConfig } from "./contentScope/useContentScopeConfig"; export { CronJobsPage } from "./cronJobs/CronJobsPage"; export { JobRuntime } from "./cronJobs/JobRuntime"; +export { DamFileDownloadLinkBlock } from "./dam/blocks/DamFileDownloadLinkBlock"; export { DamImageBlock } from "./dam/blocks/DamImageBlock"; export { DamConfigProvider } from "./dam/config/DamConfigProvider"; export { damDefaultAcceptedMimeTypes } from "./dam/config/damDefaultAcceptedMimeTypes"; diff --git a/packages/api/cms-api/block-meta.json b/packages/api/cms-api/block-meta.json index 5812f16f97..7dfdc23a4c 100644 --- a/packages/api/cms-api/block-meta.json +++ b/packages/api/cms-api/block-meta.json @@ -16,6 +16,60 @@ } ] }, + { + "name": "DamFileDownloadLink", + "fields": [ + { + "name": "file", + "kind": "NestedObject", + "object": { + "fields": [ + { + "name": "id", + "kind": "String", + "nullable": false + }, + { + "name": "name", + "kind": "String", + "nullable": false + }, + { + "name": "fileUrl", + "kind": "String", + "nullable": false + } + ] + }, + "nullable": true + }, + { + "name": "openFileType", + "kind": "Enum", + "enum": [ + "NewTab", + "Download" + ], + "nullable": false + } + ], + "inputFields": [ + { + "name": "fileId", + "kind": "String", + "nullable": true + }, + { + "name": "openFileType", + "kind": "Enum", + "enum": [ + "NewTab", + "Download" + ], + "nullable": false + } + ] + }, { "name": "DamImage", "fields": [ diff --git a/packages/api/cms-api/src/dam/blocks/dam-file-download-link.block.ts b/packages/api/cms-api/src/dam/blocks/dam-file-download-link.block.ts new file mode 100644 index 0000000000..99d33659d5 --- /dev/null +++ b/packages/api/cms-api/src/dam/blocks/dam-file-download-link.block.ts @@ -0,0 +1,128 @@ +import { + AnnotationBlockMeta, + BlockContext, + BlockData, + BlockField, + BlockIndexData, + BlockInput, + BlockMetaField, + BlockMetaFieldKind, + createBlock, + inputToData, + TraversableTransformResponse, +} from "@comet/blocks-api"; +import { IsEnum, IsUUID } from "class-validator"; +import { FilesService } from "src/dam/files/files.service"; + +import { IsUndefinable } from "../../common/validators/is-undefinable"; +import { FILE_ENTITY } from "../../dam/files/entities/file.entity"; + +export enum OpenFileTypeMethod { + NewTab = "NewTab", + Download = "Download", +} + +class DamFileDownloadLinkBlockData extends BlockData { + fileId?: string; + + @BlockField({ type: "enum", enum: OpenFileTypeMethod }) + openFileType: OpenFileTypeMethod; + + async transformToPlain( + { filesService }: { filesService: FilesService }, + { previewDamUrls }: BlockContext, + ): Promise { + const ret: TraversableTransformResponse = { + openFileType: this.openFileType, + }; + + if (this.fileId === undefined) { + return ret; + } + + const file = await filesService.findOneById(this.fileId); + + if (file && this.openFileType === "NewTab") { + ret.file = { + id: file.id, + name: file.name, + fileUrl: await filesService.createFileUrl(file, previewDamUrls), + }; + } else if (file && this.openFileType === "Download") { + ret.file = { + id: file.id, + name: file.name, + fileUrl: await filesService.createFileDownloadUrl(file, previewDamUrls), + }; + } + + return ret; + } + + indexData(): BlockIndexData { + if (this.fileId === undefined) { + return {}; + } + + return { + dependencies: [ + { + targetEntityName: FILE_ENTITY, + id: this.fileId, + }, + ], + }; + } +} + +class DamFileDownloadLinkBlockInput extends BlockInput { + @IsUndefinable() + @IsUUID() + @BlockField({ nullable: true }) + fileId?: string; + + @IsEnum(OpenFileTypeMethod) + @BlockField({ type: "enum", enum: OpenFileTypeMethod }) + openFileType: OpenFileTypeMethod; + + transformToBlockData(): DamFileDownloadLinkBlockData { + return inputToData(DamFileDownloadLinkBlockData, this); + } +} + +class Meta extends AnnotationBlockMeta { + get fields(): BlockMetaField[] { + return [ + { + name: "file", + kind: BlockMetaFieldKind.NestedObject, + nullable: true, + object: { + fields: [ + { + name: "id", + kind: BlockMetaFieldKind.String, + nullable: false, + }, + { + name: "name", + kind: BlockMetaFieldKind.String, + nullable: false, + }, + { + name: "fileUrl", + kind: BlockMetaFieldKind.String, + nullable: false, + }, + ], + }, + }, + ...super.fields, + ]; + } +} + +export const DamFileDownloadLinkBlock = createBlock(DamFileDownloadLinkBlockData, DamFileDownloadLinkBlockInput, { + name: "DamFileDownloadLink", + blockMeta: new Meta(DamFileDownloadLinkBlockData), +}); diff --git a/packages/api/cms-api/src/dam/files/files.controller.ts b/packages/api/cms-api/src/dam/files/files.controller.ts index dd41ec651d..0f402212df 100644 --- a/packages/api/cms-api/src/dam/files/files.controller.ts +++ b/packages/api/cms-api/src/dam/files/files.controller.ts @@ -107,6 +107,51 @@ export function createFilesController({ Scope: PassedScope }: { Scope?: Type { + const file = await this.filesService.findOneById(fileId); + + if (file === null) { + throw new NotFoundException(); + } + + if (file.scope !== undefined && !this.accessControlService.isAllowed(user, "dam", file.scope)) { + throw new ForbiddenException(); + } + + res.setHeader("Content-Disposition", "attachment"); + return this.streamFile(file, res, { range, overrideHeaders: { "Cache-control": "private" } }); + } + + @DisableGlobalGuard() + @Get(`/download/:hash/${fileUrl}`) + async downloadFile( + @Param() { hash, ...params }: HashFileParams, + @Res() res: Response, + @Headers(CDN_ORIGIN_CHECK_HEADER) cdnOriginCheck: string, + @Headers("range") range?: string, + ): Promise { + this.checkCdnOrigin(cdnOriginCheck); + + if (!this.isValidHash(hash, params)) { + throw new NotFoundException(); + } + + const file = await this.filesService.findOneById(params.fileId); + + if (file === null) { + throw new NotFoundException(); + } + + res.setHeader("Content-Disposition", "attachment"); + return this.streamFile(file, res, { range }); + } + @DisableGlobalGuard() @Get(`/:hash/${fileUrl}`) async hashedFileUrl( diff --git a/packages/api/cms-api/src/dam/files/files.service.ts b/packages/api/cms-api/src/dam/files/files.service.ts index 2ccbd9501c..a28e781307 100644 --- a/packages/api/cms-api/src/dam/files/files.service.ts +++ b/packages/api/cms-api/src/dam/files/files.service.ts @@ -550,6 +550,28 @@ export class FilesService { return [...baseUrl, file.id, filename].join("/"); } + async createFileDownloadUrl(file: FileInterface, previewDamUrls?: boolean): Promise { + const filename = parse(file.name).name; + + // Use CDN url only if available and not in preview as preview requires auth + const baseUrl = [ + this.config.cdnEnabled && !previewDamUrls ? `${this.config.cdnDomain}/files/download` : `${this.config.filesBaseUrl}/download`, + ]; + + if (previewDamUrls) { + baseUrl.push("preview"); + } else { + const hash = this.createHash({ + fileId: file.id, + filename, + }); + + baseUrl.push(hash); + } + + return [...baseUrl, file.id, filename].join("/"); + } + createHash(params: FileParams): string { const fileHash = `file:${params.fileId}:${params.filename}`; return createHmac("sha1", this.config.secret).update(fileHash).digest("hex"); diff --git a/packages/api/cms-api/src/index.ts b/packages/api/cms-api/src/index.ts index 45d4c820a0..63d9ebe202 100644 --- a/packages/api/cms-api/src/index.ts +++ b/packages/api/cms-api/src/index.ts @@ -62,6 +62,7 @@ export { IsNullable } from "./common/validators/is-nullable"; export { IsSlug } from "./common/validators/is-slug"; export { IsUndefinable } from "./common/validators/is-undefinable"; export { CronJobsModule } from "./cron-jobs/cron-jobs.module"; +export { DamFileDownloadLinkBlock } from "./dam/blocks/dam-file-download-link.block"; export { DamImageBlock } from "./dam/blocks/dam-image.block"; export { ScaledImagesCacheService } from "./dam/cache/scaled-images-cache.service"; export { FocalPoint } from "./dam/common/enums/focal-point.enum"; diff --git a/packages/site/cms-site/src/blocks/DamFileDownloadLinkBlock.tsx b/packages/site/cms-site/src/blocks/DamFileDownloadLinkBlock.tsx new file mode 100644 index 0000000000..41512f8ee8 --- /dev/null +++ b/packages/site/cms-site/src/blocks/DamFileDownloadLinkBlock.tsx @@ -0,0 +1,33 @@ +import * as React from "react"; + +import { DamFileDownloadLinkBlockData } from "../blocks.generated"; +import { withPreview } from "../iframebridge/withPreview"; +import { PropsWithData } from "./PropsWithData"; + +interface Props extends PropsWithData { + children: React.ReactNode; + title?: string; +} + +export const DamFileDownloadLinkBlock = withPreview( + ({ data: { file, openFileType }, children, title }: Props) => { + if (file === undefined) { + return <>{children}; + } + + if (openFileType === "Download") { + return ( + + {children} + + ); + } else { + return ( + + {children} + + ); + } + }, + { label: "DamFileDownloadLink" }, +); diff --git a/packages/site/cms-site/src/index.ts b/packages/site/cms-site/src/index.ts index 5db1f75bcb..927e1a6027 100644 --- a/packages/site/cms-site/src/index.ts +++ b/packages/site/cms-site/src/index.ts @@ -1,3 +1,4 @@ +export { DamFileDownloadLinkBlock } from "./blocks/DamFileDownloadLinkBlock"; export { ExternalLinkBlock } from "./blocks/ExternalLinkBlock"; export { BlocksBlock } from "./blocks/factories/BlocksBlock"; export { ListBlock } from "./blocks/factories/ListBlock"; From 16ffa7be965768fc3675616bd7289b0bfe461d00 Mon Sep 17 00:00:00 2001 From: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:17:33 +0200 Subject: [PATCH 11/11] Add `FinalFormAsyncSelect`, `AsyncSelectField`, and `FinalFormAsyncAutocomplete` components (#2069) Thin wrappers to ease using `useAsyncOptionsProps()` with `FinalFormSelect` and `FinalFormAutocomplete`. **Example** Previously: ```tsx const asyncOptionsProps = useAsyncOptionsProps(async () => { // Load options here }); // ... ; ``` Now: ```tsx { // Load options here }} /> ``` --- .changeset/fifty-lies-smell.md | 32 +++++++++++++++++++ .../src/form/FinalFormAsyncAutocomplete.tsx | 22 +++++++++++++ .../admin/src/form/FinalFormAsyncSelect.tsx | 13 ++++++++ .../src/form/fields/AsyncSelectField.tsx | 15 +++++++++ packages/admin/admin/src/index.ts | 3 ++ .../src/admin/form/AllFieldComponents.tsx | 10 ++++++ .../components/FinalFormFields.stories.mdx | 2 +- .../stories/FinalFormFields.stories.tsx | 24 ++++++-------- 8 files changed, 105 insertions(+), 16 deletions(-) create mode 100644 .changeset/fifty-lies-smell.md create mode 100644 packages/admin/admin/src/form/FinalFormAsyncAutocomplete.tsx create mode 100644 packages/admin/admin/src/form/FinalFormAsyncSelect.tsx create mode 100644 packages/admin/admin/src/form/fields/AsyncSelectField.tsx diff --git a/.changeset/fifty-lies-smell.md b/.changeset/fifty-lies-smell.md new file mode 100644 index 0000000000..6f019907ef --- /dev/null +++ b/.changeset/fifty-lies-smell.md @@ -0,0 +1,32 @@ +--- +"@comet/admin": minor +--- + +Add `FinalFormAsyncSelect`, `AsyncSelectField`, and `FinalFormAsyncAutocomplete` components + +Thin wrappers to ease using `useAsyncOptionsProps()` with `FinalFormSelect` and `FinalFormAutocomplete`. + +**Example** + +Previously: + +```tsx +const asyncOptionsProps = useAsyncOptionsProps(async () => { + // Load options here +}); + +// ... + +; +``` + +Now: + +```tsx + { + // Load options here + }} +/> +``` diff --git a/packages/admin/admin/src/form/FinalFormAsyncAutocomplete.tsx b/packages/admin/admin/src/form/FinalFormAsyncAutocomplete.tsx new file mode 100644 index 0000000000..988092084b --- /dev/null +++ b/packages/admin/admin/src/form/FinalFormAsyncAutocomplete.tsx @@ -0,0 +1,22 @@ +import React from "react"; + +import { useAsyncOptionsProps } from "../hooks/useAsyncOptionsProps"; +import FinalFormAutocomplete, { FinalFormAutocompleteProps } from "./Autocomplete"; + +export interface FinalFormAsyncAutocompleteProps< + T extends Record, + Multiple extends boolean | undefined, + DisableClearable extends boolean | undefined, + FreeSolo extends boolean | undefined, +> extends FinalFormAutocompleteProps { + loadOptions: () => Promise; +} + +export function FinalFormAsyncAutocomplete< + T extends Record, + Multiple extends boolean | undefined, + DisableClearable extends boolean | undefined, + FreeSolo extends boolean | undefined, +>({ loadOptions, ...rest }: FinalFormAsyncAutocompleteProps) { + return {...useAsyncOptionsProps(loadOptions)} {...rest} />; +} diff --git a/packages/admin/admin/src/form/FinalFormAsyncSelect.tsx b/packages/admin/admin/src/form/FinalFormAsyncSelect.tsx new file mode 100644 index 0000000000..a6911cec46 --- /dev/null +++ b/packages/admin/admin/src/form/FinalFormAsyncSelect.tsx @@ -0,0 +1,13 @@ +import { SelectProps } from "@mui/material"; +import React from "react"; + +import { useAsyncOptionsProps } from "../hooks/useAsyncOptionsProps"; +import { FinalFormSelect, FinalFormSelectProps } from "./FinalFormSelect"; + +export interface FinalFormAsyncSelectProps extends FinalFormSelectProps, Omit { + loadOptions: () => Promise; +} + +export function FinalFormAsyncSelect({ loadOptions, ...rest }: FinalFormAsyncSelectProps) { + return {...useAsyncOptionsProps(loadOptions)} {...rest} />; +} diff --git a/packages/admin/admin/src/form/fields/AsyncSelectField.tsx b/packages/admin/admin/src/form/fields/AsyncSelectField.tsx new file mode 100644 index 0000000000..7fc7167f43 --- /dev/null +++ b/packages/admin/admin/src/form/fields/AsyncSelectField.tsx @@ -0,0 +1,15 @@ +import React from "react"; + +import { Field, FieldProps } from "../Field"; +import { FinalFormAsyncSelect } from "../FinalFormAsyncSelect"; + +export interface AsyncSelectFieldProps