diff --git a/demo/admin/crud-generator-config.ts b/demo/admin/crud-generator-config.ts index 2b2b8f912f..ff6b3b7dbd 100644 --- a/demo/admin/crud-generator-config.ts +++ b/demo/admin/crud-generator-config.ts @@ -1,4 +1,5 @@ import { CrudGeneratorConfig } from "@comet/cms-admin"; + export default [ { target: "src/products/generated", @@ -8,4 +9,8 @@ export default [ target: "src/news/generated", entityName: "News", }, + { + target: "src/warnings/generated", + entityName: "Warning", + }, ] satisfies CrudGeneratorConfig[]; diff --git a/demo/admin/src/common/MasterMenu.tsx b/demo/admin/src/common/MasterMenu.tsx index 3f6b15b1a7..9905291e59 100644 --- a/demo/admin/src/common/MasterMenu.tsx +++ b/demo/admin/src/common/MasterMenu.tsx @@ -32,6 +32,7 @@ import { ProductsPage } from "@src/products/generated/ProductsPage"; import { ManufacturersPage as ManufacturersHandmadePage } from "@src/products/ManufacturersPage"; import ProductsHandmadePage from "@src/products/ProductsPage"; import ProductTagsPage from "@src/products/tags/ProductTagsPage"; +import { WarningsPage } from "@src/warnings/generated/WarningsPage"; import { FormattedMessage } from "react-intl"; import { Redirect, RouteComponentProps } from "react-router-dom"; @@ -175,6 +176,15 @@ export const masterMenuData: MasterMenuData = [ }, requiredPermission: "pageTree", }, + { + type: "route", + primary: , + route: { + path: "/system/warnings", + component: WarningsPage, + }, + requiredPermission: "pageTree", + }, ], requiredPermission: "pageTree", }, diff --git a/demo/admin/src/warnings/generated/WarningForm.gql.ts b/demo/admin/src/warnings/generated/WarningForm.gql.ts new file mode 100644 index 0000000000..4faa2364ef --- /dev/null +++ b/demo/admin/src/warnings/generated/WarningForm.gql.ts @@ -0,0 +1,53 @@ +// This file has been generated by comet admin-generator. +// You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment. + +import { gql } from "@apollo/client"; + +export const warningFormFragment = gql` + fragment WarningForm on Warning { + type + level + state + } +`; + +export const warningFormQuery = gql` + query WarningForm($id: ID!) { + warning(id: $id) { + id + updatedAt + ...WarningForm + } + } + ${warningFormFragment} +`; + +export const warningFormCheckForChangesQuery = gql` + query WarningFormCheckForChanges($id: ID!) { + warning(id: $id) { + updatedAt + } + } +`; + +export const createWarningMutation = gql` + mutation CreateWarning($input: WarningInput!) { + createWarning(input: $input) { + id + updatedAt + ...WarningForm + } + } + ${warningFormFragment} +`; + +export const updateWarningMutation = gql` + mutation UpdateWarning($id: ID!, $input: WarningUpdateInput!) { + updateWarning(id: $id, input: $input) { + id + updatedAt + ...WarningForm + } + } + ${warningFormFragment} +`; diff --git a/demo/admin/src/warnings/generated/WarningForm.tsx b/demo/admin/src/warnings/generated/WarningForm.tsx new file mode 100644 index 0000000000..1b031ed650 --- /dev/null +++ b/demo/admin/src/warnings/generated/WarningForm.tsx @@ -0,0 +1,182 @@ +// This file has been generated by comet admin-generator. +// You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment. + +import { useApolloClient, useQuery } from "@apollo/client"; +import { + Field, + filterByFragment, + FinalForm, + FinalFormSaveButton, + FinalFormSelect, + FinalFormSubmitEvent, + Loading, + MainContent, + TextField, + Toolbar, + ToolbarActions, + ToolbarFillSpace, + ToolbarItem, + ToolbarTitleItem, + useFormApiRef, + useStackApi, + useStackSwitchApi, +} from "@comet/admin"; +import { ArrowLeft } from "@comet/admin-icons"; +import { ContentScopeIndicator, queryUpdatedAt, resolveHasSaveConflict, useFormSaveConflict } from "@comet/cms-admin"; +import { IconButton, MenuItem } from "@mui/material"; +import { FormApi } from "final-form"; +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import { createWarningMutation, updateWarningMutation, warningFormFragment, warningFormQuery } from "./WarningForm.gql"; +import { + GQLCreateWarningMutation, + GQLCreateWarningMutationVariables, + GQLUpdateWarningMutation, + GQLUpdateWarningMutationVariables, + GQLWarningFormFragment, + GQLWarningFormQuery, + GQLWarningFormQueryVariables, +} from "./WarningForm.gql.generated"; + +type FormValues = GQLWarningFormFragment; + +interface FormProps { + id?: string; +} + +export function WarningForm({ id }: FormProps): React.ReactElement { + const stackApi = useStackApi(); + const client = useApolloClient(); + const mode = id ? "edit" : "add"; + const formApiRef = useFormApiRef(); + const stackSwitchApi = useStackSwitchApi(); + + const { data, error, loading, refetch } = useQuery( + warningFormQuery, + id ? { variables: { id } } : { skip: true }, + ); + + const initialValues = React.useMemo>( + () => + data?.warning + ? { + ...filterByFragment(warningFormFragment, data.warning), + } + : {}, + [data], + ); + + const saveConflict = useFormSaveConflict({ + checkConflict: async () => { + const updatedAt = await queryUpdatedAt(client, "warning", id); + return resolveHasSaveConflict(data?.warning.updatedAt, updatedAt); + }, + formApiRef, + loadLatestVersion: async () => { + await refetch(); + }, + }); + + const handleSubmit = async (state: FormValues, form: FormApi, event: FinalFormSubmitEvent) => { + if (await saveConflict.checkForConflicts()) { + throw new Error("Conflicts detected"); + } + + const output = { + ...state, + }; + + if (mode === "edit") { + if (!id) { + throw new Error("Missing id in edit mode"); + } + await client.mutate({ + mutation: updateWarningMutation, + variables: { id, input: output }, + }); + } else { + const { data: mutationResponse } = await client.mutate({ + mutation: createWarningMutation, + variables: { input: output }, + }); + if (!event.navigatingBack) { + const id = mutationResponse?.createWarning.id; + if (id) { + setTimeout(() => { + stackSwitchApi.activatePage("edit", id); + }); + } + } + } + }; + + if (error) throw error; + + if (loading) { + return ; + } + + return ( + apiRef={formApiRef} onSubmit={handleSubmit} mode={mode} initialValues={initialValues}> + {({ values }) => ( + <> + {saveConflict.dialogs} + }> + + + + + + + + + + + + + + + } + /> + }> + {(props) => ( + + + + + + + + + + + + )} + + }> + {(props) => ( + + + + + + + + + + + + )} + + + + )} + + ); +} diff --git a/demo/admin/src/warnings/generated/WarningsGrid.tsx b/demo/admin/src/warnings/generated/WarningsGrid.tsx new file mode 100644 index 0000000000..b5f7552fdb --- /dev/null +++ b/demo/admin/src/warnings/generated/WarningsGrid.tsx @@ -0,0 +1,206 @@ +// This file has been generated by comet admin-generator. +// You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment. +import { gql, useApolloClient, useQuery } from "@apollo/client"; +import { + CrudContextMenu, + DataGridToolbar, + GridColDef, + GridFilterButton, + MainContent, + muiGridFilterToGql, + muiGridSortToGql, + StackLink, + ToolbarActions, + ToolbarFillSpace, + ToolbarItem, + useBufferedRowCount, + useDataGridRemote, + usePersistentColumnState, +} from "@comet/admin"; +import { Add as AddIcon, Edit } from "@comet/admin-icons"; +import { Button, IconButton } from "@mui/material"; +import { DataGridPro, GridToolbarQuickFilter } from "@mui/x-data-grid-pro"; +import * as React from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import { + GQLCreateWarningMutation, + GQLCreateWarningMutationVariables, + GQLDeleteWarningMutation, + GQLDeleteWarningMutationVariables, + GQLWarningsGridQuery, + GQLWarningsGridQueryVariables, + GQLWarningsListFragment, +} from "./WarningsGrid.generated"; + +const warningsFragment = gql` + fragment WarningsList on Warning { + id + createdAt + updatedAt + type + level + state + } +`; + +const warningsQuery = gql` + query WarningsGrid($offset: Int, $limit: Int, $sort: [WarningSort!], $search: String, $filter: WarningFilter) { + warnings(offset: $offset, limit: $limit, sort: $sort, search: $search, filter: $filter) { + nodes { + ...WarningsList + } + totalCount + } + } + ${warningsFragment} +`; + +const deleteWarningMutation = gql` + mutation DeleteWarning($id: ID!) { + deleteWarning(id: $id) + } +`; + +const createWarningMutation = gql` + mutation CreateWarning($input: WarningInput!) { + createWarning(input: $input) { + id + } + } +`; + +function WarningsGridToolbar() { + return ( + + + + + + + + + + + + + ); +} + +export function WarningsGrid(): React.ReactElement { + const client = useApolloClient(); + const intl = useIntl(); + const dataGridProps = { ...useDataGridRemote(), ...usePersistentColumnState("WarningsGrid") }; + + const columns: GridColDef[] = [ + { + field: "createdAt", + headerName: intl.formatMessage({ id: "warning.createdAt", defaultMessage: "Created At" }), + type: "dateTime", + valueGetter: ({ value }) => value && new Date(value), + width: 150, + }, + { + field: "updatedAt", + headerName: intl.formatMessage({ id: "warning.updatedAt", defaultMessage: "Updated At" }), + type: "dateTime", + valueGetter: ({ value }) => value && new Date(value), + width: 150, + }, + { field: "type", headerName: intl.formatMessage({ id: "warning.type", defaultMessage: "Type" }), width: 150 }, + { + field: "level", + headerName: intl.formatMessage({ id: "warning.level", defaultMessage: "Level" }), + type: "singleSelect", + valueOptions: [ + { value: "critical", label: intl.formatMessage({ id: "warning.level.critical", defaultMessage: "Critical" }) }, + { value: "high", label: intl.formatMessage({ id: "warning.level.high", defaultMessage: "High" }) }, + { value: "low", label: intl.formatMessage({ id: "warning.level.low", defaultMessage: "Low" }) }, + ], + width: 150, + }, + { + field: "state", + headerName: intl.formatMessage({ id: "warning.state", defaultMessage: "State" }), + type: "singleSelect", + valueOptions: [ + { value: "open", label: intl.formatMessage({ id: "warning.state.open", defaultMessage: "Open" }) }, + { value: "resolved", label: intl.formatMessage({ id: "warning.state.resolved", defaultMessage: "Resolved" }) }, + { value: "ignored", label: intl.formatMessage({ id: "warning.state.ignored", defaultMessage: "Ignored" }) }, + ], + width: 150, + }, + { + field: "actions", + headerName: "", + sortable: false, + filterable: false, + type: "actions", + renderCell: (params) => { + return ( + <> + + + + { + const row = params.row; + return { + type: row.type, + level: row.level, + state: row.state, + }; + }} + onPaste={async ({ input }) => { + await client.mutate({ + mutation: createWarningMutation, + variables: { input }, + }); + }} + onDelete={async () => { + await client.mutate({ + mutation: deleteWarningMutation, + variables: { id: params.row.id }, + }); + }} + refetchQueries={[warningsQuery]} + /> + + ); + }, + }, + ]; + + const { filter: gqlFilter, search: gqlSearch } = muiGridFilterToGql(columns, dataGridProps.filterModel); + + const { data, loading, error } = useQuery(warningsQuery, { + variables: { + filter: gqlFilter, + search: gqlSearch, + offset: dataGridProps.page * dataGridProps.pageSize, + limit: dataGridProps.pageSize, + sort: muiGridSortToGql(dataGridProps.sortModel), + }, + }); + const rowCount = useBufferedRowCount(data?.warnings.totalCount); + if (error) throw error; + const rows = data?.warnings.nodes ?? []; + + return ( + + + + ); +} diff --git a/demo/admin/src/warnings/generated/WarningsPage.tsx b/demo/admin/src/warnings/generated/WarningsPage.tsx new file mode 100644 index 0000000000..d639b8e19b --- /dev/null +++ b/demo/admin/src/warnings/generated/WarningsPage.tsx @@ -0,0 +1,30 @@ +// This file has been generated by comet admin-generator. +// You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment. + +import { Stack, StackPage, StackSwitch, StackToolbar } from "@comet/admin"; +import { ContentScopeIndicator } from "@comet/cms-admin"; +import * as React from "react"; +import { useIntl } from "react-intl"; + +import { WarningForm } from "./WarningForm"; +import { WarningsGrid } from "./WarningsGrid"; + +export function WarningsPage(): React.ReactElement { + const intl = useIntl(); + return ( + + + + } /> + + + + {(selectedId) => } + + + + + + + ); +} diff --git a/demo/api/schema.gql b/demo/api/schema.gql index a87b1f2453..5cf2ece4ce 100644 --- a/demo/api/schema.gql +++ b/demo/api/schema.gql @@ -605,6 +605,32 @@ type RedirectScope { domain: String! } +type Warning { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + type: String! + level: WarningLevel! + state: WarningState! +} + +enum WarningLevel { + critical + high + low +} + +enum WarningState { + open + resolved + ignored +} + +type PaginatedWarnings { + nodes: [Warning!]! + totalCount: Int! +} + type PaginatedPageTreeNodes { nodes: [PageTreeNode!]! totalCount: Int! @@ -790,6 +816,8 @@ type Query { manufacturers(offset: Int! = 0, limit: Int! = 25, search: String, filter: ManufacturerFilter, sort: [ManufacturerSort!]): PaginatedManufacturers! manufacturerCountry(id: ID!): ManufacturerCountry! manufacturerCountries(offset: Int! = 0, limit: Int! = 25, search: String, filter: ManufacturerCountryFilter): PaginatedManufacturerCountries! + warning(id: ID!): Warning! + warnings(offset: Int! = 0, limit: Int! = 25, search: String, filter: WarningFilter, sort: [WarningSort!]): PaginatedWarnings! } input UserFilter { @@ -1149,6 +1177,41 @@ input ManufacturerCountryFilter { or: [ManufacturerCountryFilter!] } +input WarningFilter { + createdAt: DateTimeFilter + updatedAt: DateTimeFilter + type: StringFilter + level: WarningLevelEnumFilter + state: WarningStateEnumFilter + and: [WarningFilter!] + or: [WarningFilter!] +} + +input WarningLevelEnumFilter { + isAnyOf: [WarningLevel!] + equal: WarningLevel + notEqual: WarningLevel +} + +input WarningStateEnumFilter { + isAnyOf: [WarningState!] + equal: WarningState + notEqual: WarningState +} + +input WarningSort { + field: WarningSortField! + direction: SortDirection! = ASC +} + +enum WarningSortField { + createdAt + updatedAt + type + level + state +} + type Mutation { currentUserSignOut: String! userPermissionsCreatePermission(userId: String!, input: UserPermissionInput!): UserPermission! @@ -1209,6 +1272,9 @@ type Mutation { createManufacturer(input: ManufacturerInput!): Manufacturer! updateManufacturer(id: ID!, input: ManufacturerUpdateInput!): Manufacturer! deleteManufacturer(id: ID!): Boolean! + createWarning(input: WarningInput!): Warning! + updateWarning(id: ID!, input: WarningUpdateInput!): Warning! + deleteWarning(id: ID!): Boolean! } input UserPermissionInput { @@ -1498,3 +1564,15 @@ input ManufacturerUpdateInput { address: AddressInput addressAsEmbeddable: AddressAsEmbeddableInput } + +input WarningInput { + type: String! + level: WarningLevel! + state: WarningState! +} + +input WarningUpdateInput { + type: String + level: WarningLevel + state: WarningState +} diff --git a/demo/api/src/app.module.ts b/demo/api/src/app.module.ts index 2973d172a5..949c0df48e 100644 --- a/demo/api/src/app.module.ts +++ b/demo/api/src/app.module.ts @@ -49,6 +49,7 @@ import { Page } from "./pages/entities/page.entity"; import { PredefinedPageModule } from "./predefined-page/predefined-page.module"; import { ProductsModule } from "./products/products.module"; import { RedirectScope } from "./redirects/dto/redirect-scope"; +import { WarningsModule } from "./warnings/warning.module"; @Module({}) export class AppModule { @@ -170,6 +171,7 @@ export class AppModule { }, }), ...(config.sentry ? [SentryModule.forRootAsync(config.sentry)] : []), + WarningsModule, ], }; } diff --git a/demo/api/src/db/migrations/Migration20240725161414.ts b/demo/api/src/db/migrations/Migration20240725161414.ts new file mode 100644 index 0000000000..ecf18e67b8 --- /dev/null +++ b/demo/api/src/db/migrations/Migration20240725161414.ts @@ -0,0 +1,13 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20240725161414 extends Migration { + + async up(): Promise { + this.addSql('create table "Warning" ("id" uuid not null, "createdAt" timestamptz(0) not null, "updatedAt" timestamptz(0) not null, "type" varchar(255) not null, "level" text check ("level" in (\'critical\', \'high\', \'low\')) not null, "state" text check ("state" in (\'open\', \'resolved\', \'ignored\')) not null, constraint "Warning_pkey" primary key ("id"));'); + } + + async down(): Promise { + this.addSql('drop table if exists "Warning" cascade;'); + } + +} diff --git a/demo/api/src/warnings/entities/warning-level.enum.ts b/demo/api/src/warnings/entities/warning-level.enum.ts new file mode 100644 index 0000000000..1b71249125 --- /dev/null +++ b/demo/api/src/warnings/entities/warning-level.enum.ts @@ -0,0 +1,10 @@ +import { registerEnumType } from "@nestjs/graphql"; + +export enum WarningLevel { + critical = "critical", + high = "high", + low = "low", +} +registerEnumType(WarningLevel, { + name: "WarningLevel", +}); diff --git a/demo/api/src/warnings/entities/warning-state.enum.ts b/demo/api/src/warnings/entities/warning-state.enum.ts new file mode 100644 index 0000000000..f54cad8b37 --- /dev/null +++ b/demo/api/src/warnings/entities/warning-state.enum.ts @@ -0,0 +1,10 @@ +import { registerEnumType } from "@nestjs/graphql"; + +export enum WarningState { + open = "open", + resolved = "resolved", + ignored = "ignored", +} +registerEnumType(WarningState, { + name: "WarningState", +}); diff --git a/demo/api/src/warnings/entities/warning.entity.ts b/demo/api/src/warnings/entities/warning.entity.ts new file mode 100644 index 0000000000..63c41fc31f --- /dev/null +++ b/demo/api/src/warnings/entities/warning.entity.ts @@ -0,0 +1,43 @@ +import { RootBlockEntity } from "@comet/blocks-api"; +import { CrudField, CrudGenerator } from "@comet/cms-api"; +import { BaseEntity, Entity, Enum, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core"; +import { Field, ID, ObjectType } from "@nestjs/graphql"; +import { v4 as uuid } from "uuid"; + +import { WarningLevel } from "./warning-level.enum"; +import { WarningState } from "./warning-state.enum"; + +@ObjectType() +@Entity() +@RootBlockEntity() +@CrudGenerator({ targetDirectory: `${__dirname}/../generated/`, requiredPermission: ["pageTree"] }) +export class Warning extends BaseEntity { + [OptionalProps]?: "createdAt" | "updatedAt" | "status"; + + @PrimaryKey({ type: "uuid" }) + @Field(() => ID) + id: string = uuid(); + + @Property() + @Field() + createdAt: Date = new Date(); + + @Property({ onUpdate: () => new Date() }) + @Field() + updatedAt: Date = new Date(); + + @Property() + @Field() + @CrudField() + type: string; // enum? might be different in projects + + @Enum({ items: () => WarningLevel }) + @Field(() => WarningLevel) + level: WarningLevel; + + // TODO: add blockInfos + + @Enum({ items: () => WarningState }) + @Field(() => WarningState) + state: WarningState; +} diff --git a/demo/api/src/warnings/generated/dto/paginated-warnings.ts b/demo/api/src/warnings/generated/dto/paginated-warnings.ts new file mode 100644 index 0000000000..7c2cf70c67 --- /dev/null +++ b/demo/api/src/warnings/generated/dto/paginated-warnings.ts @@ -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 { Warning } from "../../entities/warning.entity"; + +@ObjectType() +export class PaginatedWarnings extends PaginatedResponseFactory.create(Warning) {} diff --git a/demo/api/src/warnings/generated/dto/warning.filter.ts b/demo/api/src/warnings/generated/dto/warning.filter.ts new file mode 100644 index 0000000000..038aca5b58 --- /dev/null +++ b/demo/api/src/warnings/generated/dto/warning.filter.ts @@ -0,0 +1,59 @@ +// 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 { createEnumFilter, DateTimeFilter, StringFilter } from "@comet/cms-api"; +import { Field, InputType } from "@nestjs/graphql"; +import { Type } from "class-transformer"; +import { IsOptional, ValidateNested } from "class-validator"; + +import { WarningLevel } from "../../entities/warning-level.enum"; +import { WarningState } from "../../entities/warning-state.enum"; + +@InputType() +class WarningLevelEnumFilter extends createEnumFilter(WarningLevel) {} +@InputType() +class WarningStateEnumFilter extends createEnumFilter(WarningState) {} + +@InputType() +export class WarningFilter { + @Field(() => DateTimeFilter, { nullable: true }) + @ValidateNested() + @IsOptional() + @Type(() => DateTimeFilter) + createdAt?: DateTimeFilter; + + @Field(() => DateTimeFilter, { nullable: true }) + @ValidateNested() + @IsOptional() + @Type(() => DateTimeFilter) + updatedAt?: DateTimeFilter; + + @Field(() => StringFilter, { nullable: true }) + @ValidateNested() + @IsOptional() + @Type(() => StringFilter) + type?: StringFilter; + + @Field(() => WarningLevelEnumFilter, { nullable: true }) + @ValidateNested() + @IsOptional() + @Type(() => WarningLevelEnumFilter) + level?: WarningLevelEnumFilter; + + @Field(() => WarningStateEnumFilter, { nullable: true }) + @ValidateNested() + @IsOptional() + @Type(() => WarningStateEnumFilter) + state?: WarningStateEnumFilter; + + @Field(() => [WarningFilter], { nullable: true }) + @Type(() => WarningFilter) + @ValidateNested({ each: true }) + @IsOptional() + and?: WarningFilter[]; + + @Field(() => [WarningFilter], { nullable: true }) + @Type(() => WarningFilter) + @ValidateNested({ each: true }) + @IsOptional() + or?: WarningFilter[]; +} diff --git a/demo/api/src/warnings/generated/dto/warning.input.ts b/demo/api/src/warnings/generated/dto/warning.input.ts new file mode 100644 index 0000000000..16e7050149 --- /dev/null +++ b/demo/api/src/warnings/generated/dto/warning.input.ts @@ -0,0 +1,29 @@ +// 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 { PartialType } from "@comet/cms-api"; +import { Field, InputType } from "@nestjs/graphql"; +import { IsEnum, IsNotEmpty, IsString } from "class-validator"; + +import { WarningLevel } from "../../entities/warning-level.enum"; +import { WarningState } from "../../entities/warning-state.enum"; + +@InputType() +export class WarningInput { + @IsNotEmpty() + @IsString() + @Field() + type: string; + + @IsNotEmpty() + @IsEnum(WarningLevel) + @Field(() => WarningLevel) + level: WarningLevel; + + @IsNotEmpty() + @IsEnum(WarningState) + @Field(() => WarningState) + state: WarningState; +} + +@InputType() +export class WarningUpdateInput extends PartialType(WarningInput) {} diff --git a/demo/api/src/warnings/generated/dto/warning.sort.ts b/demo/api/src/warnings/generated/dto/warning.sort.ts new file mode 100644 index 0000000000..c68e246aad --- /dev/null +++ b/demo/api/src/warnings/generated/dto/warning.sort.ts @@ -0,0 +1,27 @@ +// 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 { SortDirection } from "@comet/cms-api"; +import { Field, InputType, registerEnumType } from "@nestjs/graphql"; +import { IsEnum } from "class-validator"; + +export enum WarningSortField { + createdAt = "createdAt", + updatedAt = "updatedAt", + type = "type", + level = "level", + state = "state", +} +registerEnumType(WarningSortField, { + name: "WarningSortField", +}); + +@InputType() +export class WarningSort { + @Field(() => WarningSortField) + @IsEnum(WarningSortField) + field: WarningSortField; + + @Field(() => SortDirection, { defaultValue: SortDirection.ASC }) + @IsEnum(SortDirection) + direction: SortDirection = SortDirection.ASC; +} diff --git a/demo/api/src/warnings/generated/dto/warnings.args.ts b/demo/api/src/warnings/generated/dto/warnings.args.ts new file mode 100644 index 0000000000..9e283c274f --- /dev/null +++ b/demo/api/src/warnings/generated/dto/warnings.args.ts @@ -0,0 +1,29 @@ +// 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 { OffsetBasedPaginationArgs } from "@comet/cms-api"; +import { ArgsType, Field } from "@nestjs/graphql"; +import { Type } from "class-transformer"; +import { IsOptional, IsString, ValidateNested } from "class-validator"; + +import { WarningFilter } from "./warning.filter"; +import { WarningSort } from "./warning.sort"; + +@ArgsType() +export class WarningsArgs extends OffsetBasedPaginationArgs { + @Field({ nullable: true }) + @IsOptional() + @IsString() + search?: string; + + @Field(() => WarningFilter, { nullable: true }) + @ValidateNested() + @Type(() => WarningFilter) + @IsOptional() + filter?: WarningFilter; + + @Field(() => [WarningSort], { nullable: true }) + @ValidateNested({ each: true }) + @Type(() => WarningSort) + @IsOptional() + sort?: WarningSort[]; +} diff --git a/demo/api/src/warnings/generated/warning.resolver.ts b/demo/api/src/warnings/generated/warning.resolver.ts new file mode 100644 index 0000000000..ec3c11e7ba --- /dev/null +++ b/demo/api/src/warnings/generated/warning.resolver.ts @@ -0,0 +1,80 @@ +// 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 { AffectedEntity, gqlArgsToMikroOrmQuery, RequiredPermission } from "@comet/cms-api"; +import { FindOptions } from "@mikro-orm/core"; +import { InjectRepository } from "@mikro-orm/nestjs"; +import { EntityManager, EntityRepository } from "@mikro-orm/postgresql"; +import { Args, ID, Mutation, Query, Resolver } from "@nestjs/graphql"; + +import { Warning } from "../entities/warning.entity"; +import { PaginatedWarnings } from "./dto/paginated-warnings"; +import { WarningInput, WarningUpdateInput } from "./dto/warning.input"; +import { WarningsArgs } from "./dto/warnings.args"; + +@Resolver(() => Warning) +@RequiredPermission(["pageTree"], { skipScopeCheck: true }) +export class WarningResolver { + constructor(private readonly entityManager: EntityManager, @InjectRepository(Warning) private readonly repository: EntityRepository) {} + + @Query(() => Warning) + @AffectedEntity(Warning) + async warning(@Args("id", { type: () => ID }) id: string): Promise { + const warning = await this.repository.findOneOrFail(id); + return warning; + } + + @Query(() => PaginatedWarnings) + async warnings(@Args() { search, filter, sort, offset, limit }: WarningsArgs): Promise { + const where = gqlArgsToMikroOrmQuery({ search, filter }, this.repository); + + const options: FindOptions = { offset, limit }; + + if (sort) { + options.orderBy = sort.map((sortItem) => { + return { + [sortItem.field]: sortItem.direction, + }; + }); + } + + const [entities, totalCount] = await this.repository.findAndCount(where, options); + return new PaginatedWarnings(entities, totalCount); + } + + @Mutation(() => Warning) + async createWarning(@Args("input", { type: () => WarningInput }) input: WarningInput): Promise { + const warning = this.repository.create({ + ...input, + }); + + await this.entityManager.flush(); + + return warning; + } + + @Mutation(() => Warning) + @AffectedEntity(Warning) + async updateWarning( + @Args("id", { type: () => ID }) id: string, + @Args("input", { type: () => WarningUpdateInput }) input: WarningUpdateInput, + ): Promise { + const warning = await this.repository.findOneOrFail(id); + + warning.assign({ + ...input, + }); + + await this.entityManager.flush(); + + return warning; + } + + @Mutation(() => Boolean) + @AffectedEntity(Warning) + async deleteWarning(@Args("id", { type: () => ID }) id: string): Promise { + const warning = await this.repository.findOneOrFail(id); + this.entityManager.remove(warning); + await this.entityManager.flush(); + return true; + } +} diff --git a/demo/api/src/warnings/warning.module.ts b/demo/api/src/warnings/warning.module.ts new file mode 100644 index 0000000000..3be79caff9 --- /dev/null +++ b/demo/api/src/warnings/warning.module.ts @@ -0,0 +1,11 @@ +import { MikroOrmModule } from "@mikro-orm/nestjs"; +import { Module } from "@nestjs/common"; + +import { Warning } from "./entities/warning.entity"; +import { WarningResolver } from "./generated/warning.resolver"; + +@Module({ + imports: [MikroOrmModule.forFeature([Warning])], + providers: [WarningResolver], +}) +export class WarningsModule {}