Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API Generator: Generate better API for Many-to-one-relations with orphanRemoval activated where the reverse side has its own API generated #1478

Merged
merged 24 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
dc1cd35
CRUD Generator: Add the concept of mainProperty that can be set in @C…
nsams Dec 4, 2023
d1e6d7c
Replace mainProperty option by detection of non-nullable ManyToOne re…
nsams Dec 6, 2023
6c617dd
Revert "Replace mainProperty option by detection of non-nullable Many…
nsams Feb 8, 2024
a479328
Remove products from category input
nsams Feb 8, 2024
876b266
rename mainProperty to rootArg
nsams Feb 8, 2024
fe98b67
Merge branch 'next' into apigen-mainproperty
nsams Feb 8, 2024
7003935
Use products permission for productVariants resolver
nsams Feb 10, 2024
05956f3
Merge branch 'next' into apigen-mainproperty
nsams Feb 13, 2024
a211d29
Adapt admin to changed api
nsams Feb 13, 2024
db23849
generate @AffectedEntity for list query and create mutation
nsams Feb 20, 2024
7666dad
fix admin: don't submit variants in form, it's not part of it's input…
nsams Feb 20, 2024
d7e22ee
Update .changeset/giant-apples-cheer.md
nsams Feb 22, 2024
1a85130
Rename internal vars mainProp->rootArgProp
nsams Feb 22, 2024
4a7fe8a
Remove duplicate color prefix, we are already in color entity
nsams Feb 22, 2024
defeb62
Replace rootArg @CrudField option with orphanRemoval detection
nsams Feb 29, 2024
0e64df5
Merge branch 'next' into apigen-mainproperty
nsams Mar 2, 2024
11de0cc
re-run api-generator
nsams Mar 2, 2024
88894a2
Omit root arg prop from filter
nsams Mar 2, 2024
8546e99
Don't include rootArg in input
nsams Mar 2, 2024
7047745
Update .changeset/giant-apples-cheer.md
johnnyomair Mar 4, 2024
4797467
update comment, is now ignored automatically
nsams Mar 4, 2024
363d47c
Update .changeset/giant-apples-cheer.md
nsams Mar 5, 2024
64e42a4
Merge branch 'next' into apigen-mainproperty
nsams Mar 5, 2024
c9e88cc
Fix input handling for rootArgProps, they are not part of input
nsams Mar 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/giant-apples-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@comet/cms-api": minor
---

API Generator: Generate better API for Many-to-one-relations with `orphanRemoval` activated where the reverse side has its own API generated

- Add `id` as argument to create mutation
- Add `id` as argument to list query
1 change: 0 additions & 1 deletion demo/admin/src/products/ProductForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ function ProductForm({ id }: FormProps): React.ReactElement {
type: formValues.type as GQLProductType,
category: formValues.category?.id,
tags: formValues.tags.map((i) => i.id),
variants: [],
articleNumbers: [],
discounts: [],
statistics: { views: 0 },
Expand Down
12 changes: 6 additions & 6 deletions demo/admin/src/products/ProductsGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,7 @@ function ProductsGrid() {
type: input.type,
category: input.category?.id,
tags: input.tags.map((tag) => tag.id),
variants: input.variants.map((variant) => ({
name: variant.name,
image: DamImageBlock.state2Output(DamImageBlock.input2State(variant.image)),
})),
colors: input.colors,
articleNumbers: input.articleNumbers,
discounts: input.discounts,
statistics: { views: 0 },
Expand Down Expand Up @@ -235,9 +232,12 @@ const productsFragment = gql`
id
title
}
variants {
image
colors {
name
hexCode
}
variants {
id
}
articleNumbers
discounts {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,12 @@ const columns: GridColDef<GQLProductsCategoriesListFragment>[] = [
onPaste={async ({ input, client }) => {
await client.mutate<GQLCreateProductCategoryMutation, GQLCreateProductCategoryMutationVariables>({
mutation: createProductMutation,
variables: { input: { ...input, products: [] } },
variables: {
input: {
title: input.title,
slug: input.slug,
},
},
});
}}
onDelete={async ({ client }) => {
Expand Down
60 changes: 54 additions & 6 deletions demo/api/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,14 @@ type ProductCategory implements DocumentInterface {
products: [Product!]!
}

type ProductColor implements DocumentInterface {
id: ID!
updatedAt: DateTime!
name: String!
hexCode: String!
createdAt: DateTime!
}

type ProductStatistics {
id: ID!
views: Float!
Expand All @@ -481,6 +489,7 @@ type ProductVariant implements DocumentInterface {
name: String!
image: DamImageBlockData!
createdAt: DateTime!
product: Product!
}

type ProductDiscounts {
Expand Down Expand Up @@ -514,6 +523,7 @@ type Product implements DocumentInterface {
createdAt: DateTime!
category: ProductCategory
manufacturer: Manufacturer
colors: [ProductColor!]!
variants: [ProductVariant!]!
tags: [ProductTag!]!
}
Expand All @@ -539,6 +549,11 @@ type PaginatedProductTags {
totalCount: Int!
}

type PaginatedProductVariants {
nodes: [ProductVariant!]!
totalCount: Int!
}

type RedirectScope {
domain: String!
}
Expand Down Expand Up @@ -721,6 +736,8 @@ type Query {
productCategories(offset: Int! = 0, limit: Int! = 25, search: String, filter: ProductCategoryFilter, sort: [ProductCategorySort!]): PaginatedProductCategories!
productTag(id: ID!): ProductTag!
productTags(offset: Int! = 0, limit: Int! = 25, search: String, filter: ProductTagFilter, sort: [ProductTagSort!]): PaginatedProductTags!
productVariant(id: ID!): ProductVariant!
productVariants(offset: Int! = 0, limit: Int! = 25, product: ID!, search: String, filter: ProductVariantFilter, sort: [ProductVariantSort!]): PaginatedProductVariants!
manufacturer(id: ID!): Manufacturer!
manufacturers(offset: Int! = 0, limit: Int! = 25, search: String, filter: ManufacturerFilter, sort: [ManufacturerSort!]): PaginatedManufacturers!
}
Expand Down Expand Up @@ -978,6 +995,26 @@ enum ProductTagSortField {
updatedAt
}

input ProductVariantFilter {
name: StringFilter
createdAt: DateFilter
updatedAt: DateFilter
and: [ProductVariantFilter!]
or: [ProductVariantFilter!]
}

input ProductVariantSort {
field: ProductVariantSortField!
direction: SortDirection! = ASC
}

enum ProductVariantSortField {
name
product
createdAt
updatedAt
}

input ManufacturerFilter {
addressAsEmbeddable_street: StringFilter
addressAsEmbeddable_streetNumber: NumberFilter
Expand Down Expand Up @@ -1065,6 +1102,9 @@ type Mutation {
createProductTag(input: ProductTagInput!): ProductTag!
updateProductTag(id: ID!, input: ProductTagUpdateInput!, lastUpdatedAt: DateTime): ProductTag!
deleteProductTag(id: ID!): Boolean!
createProductVariant(product: ID!, input: ProductVariantInput!): ProductVariant!
updateProductVariant(id: ID!, input: ProductVariantUpdateInput!, lastUpdatedAt: DateTime): ProductVariant!
deleteProductVariant(id: ID!): Boolean!
createManufacturer(input: ManufacturerInput!): Manufacturer!
updateManufacturer(id: ID!, input: ManufacturerUpdateInput!, lastUpdatedAt: DateTime): Manufacturer!
deleteManufacturer(id: ID!): Boolean!
Expand Down Expand Up @@ -1256,7 +1296,7 @@ input ProductInput {
articleNumbers: [String!]! = []
dimensions: ProductDimensionsInput
statistics: ProductStatisticsInput
variants: [ProductVariantInput!]! = []
colors: [ProductColorInput!]! = []
category: ID = null
tags: [ID!]! = []
manufacturer: ID
Expand All @@ -1266,9 +1306,9 @@ input ProductStatisticsInput {
views: Float!
}

input ProductVariantInput {
input ProductColorInput {
name: String!
image: DamImageBlockInput!
hexCode: String!
}

input ProductUpdateInput {
Expand All @@ -1284,7 +1324,7 @@ input ProductUpdateInput {
articleNumbers: [String!]
dimensions: ProductDimensionsInput
statistics: ProductStatisticsInput
variants: [ProductVariantInput!]
colors: [ProductColorInput!]
category: ID
tags: [ID!]
manufacturer: ID
Expand All @@ -1293,13 +1333,11 @@ input ProductUpdateInput {
input ProductCategoryInput {
title: String!
slug: String!
products: [ID!]! = []
}

input ProductCategoryUpdateInput {
title: String
slug: String
products: [ID!]
}

input ProductTagInput {
Expand All @@ -1312,6 +1350,16 @@ input ProductTagUpdateInput {
products: [ID!]
}

input ProductVariantInput {
name: String!
image: DamImageBlockInput!
}

input ProductVariantUpdateInput {
name: String
image: DamImageBlockInput
}

input ManufacturerInput {
address: AddressInput
addressAsEmbeddable: AddressAsEmbeddableInput!
Expand Down
15 changes: 15 additions & 0 deletions demo/api/src/db/migrations/Migration20231204165948.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Migration } from '@mikro-orm/migrations';

export class Migration20231204165948 extends Migration {

async up(): Promise<void> {
this.addSql('create table "ProductColor" ("id" uuid not null, "name" varchar(255) not null, "hexCode" varchar(255) not null, "product" uuid not null, "createdAt" timestamptz(0) not null, "updatedAt" timestamptz(0) not null, constraint "ProductColor_pkey" primary key ("id"));');

this.addSql('alter table "ProductColor" add constraint "ProductColor_product_foreign" foreign key ("product") references "Product" ("id") on update cascade;');
}

async down(): Promise<void> {
this.addSql('drop table if exists "ProductColor" cascade;');
}

}
2 changes: 1 addition & 1 deletion demo/api/src/products/entities/product-category.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class ProductCategory extends BaseEntity<ProductCategory, "id"> implement
//search: true, //not implemented
//filter: true, //not implemented
//sort: true, //not implemented
input: true, //default is true
input: false, //default is true
})
@OneToMany(() => Product, (products) => products.category)
products = new Collection<Product>(this);
Expand Down
44 changes: 44 additions & 0 deletions demo/api/src/products/entities/product-color.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { CrudField, DocumentInterface } from "@comet/cms-api";
import { BaseEntity, Entity, ManyToOne, OptionalProps, PrimaryKey, Property, Ref } from "@mikro-orm/core";
import { Field, ID, ObjectType } from "@nestjs/graphql";
import { v4 as uuid } from "uuid";

import { Product } from "./product.entity";

@ObjectType({
implements: () => [DocumentInterface],
})
@Entity()
export class ProductColor extends BaseEntity<ProductColor, "id"> implements DocumentInterface {
[OptionalProps]?: "createdAt" | "updatedAt";

@PrimaryKey({ type: "uuid" })
@Field(() => ID)
id: string = uuid();

@Property()
@Field()
name: string;

@Property()
@Field()
hexCode: string;

@ManyToOne(() => Product, { ref: true })
@CrudField({
resolveField: true, // default is true
// search: true, // not yet supported for nested
// filter: true, // not yet supported for nested
// sort: true, // not yet supported for nested
// input: true, // not supported for nested, doesn't make sense
})
product: Ref<Product>;

@Property()
@Field()
createdAt: Date = new Date();

@Property({ onUpdate: () => new Date() })
@Field()
updatedAt: Date = new Date();
}
5 changes: 3 additions & 2 deletions demo/api/src/products/entities/product-variant.entity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BlockDataInterface, RootBlock, RootBlockEntity } from "@comet/blocks-api";
import { CrudField, DamImageBlock, DocumentInterface, RootBlockDataScalar, RootBlockType } from "@comet/cms-api";
import { CrudField, CrudGenerator, DamImageBlock, DocumentInterface, RootBlockDataScalar, RootBlockType } from "@comet/cms-api";
import { BaseEntity, Entity, ManyToOne, OptionalProps, PrimaryKey, Property, Ref } from "@mikro-orm/core";
import { Field, ID, ObjectType } from "@nestjs/graphql";
import { v4 as uuid } from "uuid";
Expand All @@ -11,6 +11,7 @@ import { Product } from "./product.entity";
})
@Entity()
@RootBlockEntity()
@CrudGenerator({ targetDirectory: `${__dirname}/../generated/`, requiredPermission: "products" })
export class ProductVariant extends BaseEntity<ProductVariant, "id"> implements DocumentInterface {
[OptionalProps]?: "createdAt" | "updatedAt";

Expand All @@ -33,7 +34,7 @@ export class ProductVariant extends BaseEntity<ProductVariant, "id"> implements
// search: true, // not yet supported for nested
// filter: true, // not yet supported for nested
// sort: true, // not yet supported for nested
// input: true, // not supported for nested, doesn't make sense
input: false, // default is true, but usually not needed if it's a rootArg
})
product: Ref<Product>;

Expand Down
13 changes: 12 additions & 1 deletion demo/api/src/products/entities/product.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { IsNumber } from "class-validator";
import { v4 as uuid } from "uuid";

import { ProductCategory } from "./product-category.entity";
import { ProductColor } from "./product-color.entity";
import { ProductStatistics } from "./product-statistics.entity";
import { ProductTag } from "./product-tag.entity";
import { ProductType } from "./product-type.enum";
Expand Down Expand Up @@ -134,14 +135,24 @@ export class Product extends BaseEntity<Product, "id"> implements DocumentInterf
@Field(() => ProductStatistics, { nullable: true })
statistics?: Ref<ProductStatistics> = undefined;

@OneToMany(() => ProductVariant, (variant) => variant.product, { orphanRemoval: true })
@OneToMany(() => ProductColor, (variant) => variant.product, { orphanRemoval: true })
@CrudField({
resolveField: true, //default is true
//search: true, //not yet implemented
//filter: true, //not yet implemented
//sort: true, //not yet implemented
input: true, //default is true
})
colors = new Collection<ProductColor>(this);

@OneToMany(() => ProductVariant, (variant) => variant.product, { orphanRemoval: true })
@CrudField({
resolveField: true, //default is true
//search: true, //not yet implemented
//filter: true, //not yet implemented
//sort: true, //not yet implemented
input: false, //default is true, disabled here because it is edited using it's own crud api
})
variants = new Collection<ProductVariant>(this);

@ManyToOne(() => ProductCategory, { nullable: true, ref: true })
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// This file has been generated by comet api-generator.
// You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment.
import { PaginatedResponseFactory } from "@comet/cms-api";
import { ObjectType } from "@nestjs/graphql";

import { ProductVariant } from "../../entities/product-variant.entity";

@ObjectType()
export class PaginatedProductVariants extends PaginatedResponseFactory.create(ProductVariant) {}
9 changes: 2 additions & 7 deletions demo/api/src/products/generated/dto/product-category.input.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// This file has been generated by comet api-generator.
// You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment.
import { IsSlug, PartialType } from "@comet/cms-api";
import { Field, ID, InputType } from "@nestjs/graphql";
import { IsArray, IsNotEmpty, IsString, IsUUID } from "class-validator";
import { Field, InputType } from "@nestjs/graphql";
import { IsNotEmpty, IsString } from "class-validator";

@InputType()
export class ProductCategoryInput {
Expand All @@ -16,11 +16,6 @@ export class ProductCategoryInput {
@IsSlug()
@Field()
slug: string;

@Field(() => [ID], { defaultValue: [] })
@IsArray()
@IsUUID(undefined, { each: true })
products: string[];
}

@InputType()
Expand Down
17 changes: 17 additions & 0 deletions demo/api/src/products/generated/dto/product-color.nested.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// This file has been generated by comet api-generator.
// You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment.
import { Field, InputType } from "@nestjs/graphql";
import { IsNotEmpty, IsString } from "class-validator";

@InputType()
export class ProductColorInput {
@IsNotEmpty()
@IsString()
@Field()
name: string;

@IsNotEmpty()
@IsString()
@Field()
hexCode: string;
}
Loading
Loading