Jelajahi Sumber

feat(core): Add `focalPoint` field to Asset entity

Relates to #93

BREAKING CHANGE: A new field, `focalPoint` has been added to the `Asset` entity which will require a database migration to add.
Michael Bromley 6 tahun lalu
induk
melakukan
1666e22947

+ 24 - 0
packages/admin-ui/src/app/common/generated-types.ts

@@ -116,6 +116,7 @@ export type Asset = Node & {
   height: Scalars['Int'],
   source: Scalars['String'],
   preview: Scalars['String'],
+  focalPoint?: Maybe<Coordinate>,
 };
 
 export type AssetFilterParameter = {
@@ -338,6 +339,17 @@ export type ConfigurableOperationInput = {
   arguments: Array<ConfigArgInput>,
 };
 
+export type Coordinate = {
+  __typename?: 'Coordinate',
+  x: Scalars['Float'],
+  y: Scalars['Float'],
+};
+
+export type CoordinateInput = {
+  x: Scalars['Float'],
+  y: Scalars['Float'],
+};
+
 export type Country = Node & {
   __typename?: 'Country',
   id: Scalars['ID'],
@@ -1697,6 +1709,7 @@ export type Mutation = {
   assignRoleToAdministrator: Administrator,
   /** Create a new Asset */
   createAssets: Array<Asset>,
+  updateAsset: Asset,
   login: LoginResult,
   logout: Scalars['Boolean'],
   /** Create a new Channel */
@@ -1858,6 +1871,11 @@ export type MutationCreateAssetsArgs = {
 };
 
 
+export type MutationUpdateAssetArgs = {
+  input: UpdateAssetInput
+};
+
+
 export type MutationLoginArgs = {
   username: Scalars['String'],
   password: Scalars['String'],
@@ -3404,6 +3422,12 @@ export type UpdateAdministratorInput = {
   roleIds?: Maybe<Array<Scalars['ID']>>,
 };
 
+export type UpdateAssetInput = {
+  id: Scalars['ID'],
+  name?: Maybe<Scalars['String']>,
+  focalPoint?: Maybe<CoordinateInput>,
+};
+
 export type UpdateChannelInput = {
   id: Scalars['ID'],
   code?: Maybe<Scalars['String']>,

+ 7 - 0
packages/common/src/generated-shop-types.ts

@@ -85,6 +85,7 @@ export type Asset = Node & {
     height: Scalars['Int'];
     source: Scalars['String'];
     preview: Scalars['String'];
+    focalPoint?: Maybe<Coordinate>;
 };
 
 export type AssetList = PaginatedList & {
@@ -250,6 +251,12 @@ export type ConfigurableOperationInput = {
     arguments: Array<ConfigArgInput>;
 };
 
+export type Coordinate = {
+    __typename?: 'Coordinate';
+    x: Scalars['Float'];
+    y: Scalars['Float'];
+};
+
 export type Country = Node & {
     __typename?: 'Country';
     id: Scalars['ID'];

+ 27 - 0
packages/common/src/generated-types.ts

@@ -115,6 +115,7 @@ export type Asset = Node & {
   height: Scalars['Int'],
   source: Scalars['String'],
   preview: Scalars['String'],
+  focalPoint?: Maybe<Coordinate>,
 };
 
 export type AssetFilterParameter = {
@@ -337,6 +338,17 @@ export type ConfigurableOperationInput = {
   arguments: Array<ConfigArgInput>,
 };
 
+export type Coordinate = {
+  __typename?: 'Coordinate',
+  x: Scalars['Float'],
+  y: Scalars['Float'],
+};
+
+export type CoordinateInput = {
+  x: Scalars['Float'],
+  y: Scalars['Float'],
+};
+
 export type Country = Node & {
   __typename?: 'Country',
   id: Scalars['ID'],
@@ -1689,6 +1701,8 @@ export type Mutation = {
   assignRoleToAdministrator: Administrator,
   /** Create a new Asset */
   createAssets: Array<Asset>,
+  /** Update an existing Asset */
+  updateAsset: Asset,
   login: LoginResult,
   logout: Scalars['Boolean'],
   /** Create a new Channel */
@@ -1843,6 +1857,11 @@ export type MutationCreateAssetsArgs = {
 };
 
 
+export type MutationUpdateAssetArgs = {
+  input: UpdateAssetInput
+};
+
+
 export type MutationLoginArgs = {
   username: Scalars['String'],
   password: Scalars['String'],
@@ -2733,7 +2752,9 @@ export type Query = {
   __typename?: 'Query',
   administrators: AdministratorList,
   administrator?: Maybe<Administrator>,
+  /** Get a list of Assets */
   assets: AssetList,
+  /** Get a single Asset by id */
   asset?: Maybe<Asset>,
   me?: Maybe<CurrentUser>,
   channels: Array<Channel>,
@@ -3356,6 +3377,12 @@ export type UpdateAdministratorInput = {
   roleIds?: Maybe<Array<Scalars['ID']>>,
 };
 
+export type UpdateAssetInput = {
+  id: Scalars['ID'],
+  name?: Maybe<Scalars['String']>,
+  focalPoint?: Maybe<CoordinateInput>,
+};
+
 export type UpdateChannelInput = {
   id: Scalars['ID'],
   code?: Maybe<Scalars['String']>,

+ 228 - 0
packages/core/e2e/asset.e2e-spec.ts

@@ -0,0 +1,228 @@
+import { omit } from '@vendure/common/lib/omit';
+import { pick } from '@vendure/common/lib/pick';
+import { createTestEnvironment } from '@vendure/testing';
+import gql from 'graphql-tag';
+import path from 'path';
+
+import { initialData } from '../../../e2e-common/e2e-initial-data';
+import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
+
+import { ASSET_FRAGMENT } from './graphql/fragments';
+import {
+    CreateAssets,
+    GetAsset,
+    GetAssetList,
+    SortOrder,
+    UpdateAsset,
+} from './graphql/generated-e2e-admin-types';
+import { GET_ASSET_LIST } from './graphql/shared-definitions';
+
+describe('Asset resolver', () => {
+    const { server, adminClient } = createTestEnvironment(testConfig);
+
+    let firstAssetId: string;
+
+    beforeAll(async () => {
+        await server.init({
+            initialData,
+            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
+            customerCount: 1,
+        });
+        await adminClient.asSuperAdmin();
+    }, TEST_SETUP_TIMEOUT_MS);
+
+    afterAll(async () => {
+        await server.destroy();
+    });
+
+    it('assets', async () => {
+        const { assets } = await adminClient.query<GetAssetList.Query, GetAssetList.Variables>(
+            GET_ASSET_LIST,
+            {
+                options: {
+                    sort: {
+                        name: SortOrder.ASC,
+                    },
+                },
+            },
+        );
+
+        expect(assets.totalItems).toBe(4);
+        expect(assets.items.map(a => omit(a, ['id']))).toEqual([
+            {
+                fileSize: 1680,
+                mimeType: 'image/jpeg',
+                name: 'alexandru-acea-686569-unsplash.jpg',
+                preview: 'test-url/test-assets/alexandru-acea-686569-unsplash__preview.jpg',
+                source: 'test-url/test-assets/alexandru-acea-686569-unsplash.jpg',
+                type: 'IMAGE',
+            },
+            {
+                fileSize: 1680,
+                mimeType: 'image/jpeg',
+                name: 'derick-david-409858-unsplash.jpg',
+                preview: 'test-url/test-assets/derick-david-409858-unsplash__preview.jpg',
+                source: 'test-url/test-assets/derick-david-409858-unsplash.jpg',
+                type: 'IMAGE',
+            },
+            {
+                fileSize: 1680,
+                mimeType: 'image/jpeg',
+                name: 'florian-olivo-1166419-unsplash.jpg',
+                preview: 'test-url/test-assets/florian-olivo-1166419-unsplash__preview.jpg',
+                source: 'test-url/test-assets/florian-olivo-1166419-unsplash.jpg',
+                type: 'IMAGE',
+            },
+            {
+                fileSize: 1680,
+                mimeType: 'image/jpeg',
+                name: 'vincent-botta-736919-unsplash.jpg',
+                preview: 'test-url/test-assets/vincent-botta-736919-unsplash__preview.jpg',
+                source: 'test-url/test-assets/vincent-botta-736919-unsplash.jpg',
+                type: 'IMAGE',
+            },
+        ]);
+
+        firstAssetId = assets.items[0].id;
+    });
+
+    it('asset', async () => {
+        const { asset } = await adminClient.query<GetAsset.Query, GetAsset.Variables>(GET_ASSET, {
+            id: firstAssetId,
+        });
+
+        expect(asset).toEqual({
+            fileSize: 1680,
+            height: 48,
+            id: firstAssetId,
+            mimeType: 'image/jpeg',
+            name: 'alexandru-acea-686569-unsplash.jpg',
+            preview: 'test-url/test-assets/alexandru-acea-686569-unsplash__preview.jpg',
+            source: 'test-url/test-assets/alexandru-acea-686569-unsplash.jpg',
+            type: 'IMAGE',
+            width: 48,
+        });
+    });
+
+    it('createAssets', async () => {
+        const filesToUpload = [
+            path.join(__dirname, 'fixtures/assets/pps1.jpg'),
+            path.join(__dirname, 'fixtures/assets/pps2.jpg'),
+        ];
+        const { createAssets }: CreateAssets.Mutation = await adminClient.fileUploadMutation({
+            mutation: CREATE_ASSETS,
+            filePaths: filesToUpload,
+            mapVariables: filePaths => ({
+                input: filePaths.map(p => ({ file: null })),
+            }),
+        });
+
+        expect(createAssets.map(a => omit(a, ['id'])).sort((a, b) => (a.name < b.name ? -1 : 1))).toEqual([
+            {
+                fileSize: 1680,
+                focalPoint: null,
+                mimeType: 'image/jpeg',
+                name: 'pps1.jpg',
+                preview: 'test-url/test-assets/pps1__preview.jpg',
+                source: 'test-url/test-assets/pps1.jpg',
+                type: 'IMAGE',
+            },
+            {
+                fileSize: 1680,
+                focalPoint: null,
+                mimeType: 'image/jpeg',
+                name: 'pps2.jpg',
+                preview: 'test-url/test-assets/pps2__preview.jpg',
+                source: 'test-url/test-assets/pps2.jpg',
+                type: 'IMAGE',
+            },
+        ]);
+    });
+
+    describe('updateAsset', () => {
+        it('update name', async () => {
+            const { updateAsset } = await adminClient.query<UpdateAsset.Mutation, UpdateAsset.Variables>(
+                UPDATE_ASSET,
+                {
+                    input: {
+                        id: firstAssetId,
+                        name: 'new name',
+                    },
+                },
+            );
+
+            expect(updateAsset.name).toEqual('new name');
+        });
+
+        it('update focalPoint', async () => {
+            const { updateAsset } = await adminClient.query<UpdateAsset.Mutation, UpdateAsset.Variables>(
+                UPDATE_ASSET,
+                {
+                    input: {
+                        id: firstAssetId,
+                        focalPoint: {
+                            x: 0.3,
+                            y: 0.9,
+                        },
+                    },
+                },
+            );
+
+            expect(updateAsset.focalPoint).toEqual({
+                x: 0.3,
+                y: 0.9,
+            });
+        });
+
+        it('unset focalPoint', async () => {
+            const { updateAsset } = await adminClient.query<UpdateAsset.Mutation, UpdateAsset.Variables>(
+                UPDATE_ASSET,
+                {
+                    input: {
+                        id: firstAssetId,
+                        focalPoint: null,
+                    },
+                },
+            );
+
+            expect(updateAsset.focalPoint).toEqual(null);
+        });
+    });
+});
+
+export const GET_ASSET = gql`
+    query GetAsset($id: ID!) {
+        asset(id: $id) {
+            ...Asset
+            width
+            height
+        }
+    }
+    ${ASSET_FRAGMENT}
+`;
+
+export const CREATE_ASSETS = gql`
+    mutation CreateAssets($input: [CreateAssetInput!]!) {
+        createAssets(input: $input) {
+            ...Asset
+            focalPoint {
+                x
+                y
+            }
+        }
+    }
+    ${ASSET_FRAGMENT}
+`;
+
+export const UPDATE_ASSET = gql`
+    mutation UpdateAsset($input: UpdateAssetInput!) {
+        updateAsset(input: $input) {
+            ...Asset
+            focalPoint {
+                x
+                y
+            }
+        }
+    }
+    ${ASSET_FRAGMENT}
+`;

+ 78 - 0
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -115,6 +115,7 @@ export type Asset = Node & {
     height: Scalars['Int'];
     source: Scalars['String'];
     preview: Scalars['String'];
+    focalPoint?: Maybe<Coordinate>;
 };
 
 export type AssetFilterParameter = {
@@ -337,6 +338,17 @@ export type ConfigurableOperationInput = {
     arguments: Array<ConfigArgInput>;
 };
 
+export type Coordinate = {
+    __typename?: 'Coordinate';
+    x: Scalars['Float'];
+    y: Scalars['Float'];
+};
+
+export type CoordinateInput = {
+    x: Scalars['Float'];
+    y: Scalars['Float'];
+};
+
 export type Country = Node & {
     __typename?: 'Country';
     id: Scalars['ID'];
@@ -1692,6 +1704,8 @@ export type Mutation = {
     assignRoleToAdministrator: Administrator;
     /** Create a new Asset */
     createAssets: Array<Asset>;
+    /** Update an existing Asset */
+    updateAsset: Asset;
     login: LoginResult;
     logout: Scalars['Boolean'];
     /** Create a new Channel */
@@ -1841,6 +1855,10 @@ export type MutationCreateAssetsArgs = {
     input: Array<CreateAssetInput>;
 };
 
+export type MutationUpdateAssetArgs = {
+    input: UpdateAssetInput;
+};
+
 export type MutationLoginArgs = {
     username: Scalars['String'];
     password: Scalars['String'];
@@ -2660,7 +2678,9 @@ export type Query = {
     __typename?: 'Query';
     administrators: AdministratorList;
     administrator?: Maybe<Administrator>;
+    /** Get a list of Assets */
     assets: AssetList;
+    /** Get a single Asset by id */
     asset?: Maybe<Asset>;
     me?: Maybe<CurrentUser>;
     channels: Array<Channel>;
@@ -3249,6 +3269,12 @@ export type UpdateAdministratorInput = {
     roleIds?: Maybe<Array<Scalars['ID']>>;
 };
 
+export type UpdateAssetInput = {
+    id: Scalars['ID'];
+    name?: Maybe<Scalars['String']>;
+    focalPoint?: Maybe<CoordinateInput>;
+};
+
 export type UpdateChannelInput = {
     id: Scalars['ID'];
     code?: Maybe<Scalars['String']>;
@@ -3466,6 +3492,36 @@ export type Q2Query = { __typename?: 'Query' } & {
     product: Maybe<{ __typename?: 'Product' } & Pick<Product, 'id' | 'name'>>;
 };
 
+export type GetAssetQueryVariables = {
+    id: Scalars['ID'];
+};
+
+export type GetAssetQuery = { __typename?: 'Query' } & {
+    asset: Maybe<{ __typename?: 'Asset' } & Pick<Asset, 'width' | 'height'> & AssetFragment>;
+};
+
+export type CreateAssetsMutationVariables = {
+    input: Array<CreateAssetInput>;
+};
+
+export type CreateAssetsMutation = { __typename?: 'Mutation' } & {
+    createAssets: Array<
+        { __typename?: 'Asset' } & {
+            focalPoint: Maybe<{ __typename?: 'Coordinate' } & Pick<Coordinate, 'x' | 'y'>>;
+        } & AssetFragment
+    >;
+};
+
+export type UpdateAssetMutationVariables = {
+    input: UpdateAssetInput;
+};
+
+export type UpdateAssetMutation = { __typename?: 'Mutation' } & {
+    updateAsset: { __typename?: 'Asset' } & {
+        focalPoint: Maybe<{ __typename?: 'Coordinate' } & Pick<Coordinate, 'x' | 'y'>>;
+    } & AssetFragment;
+};
+
 export type CanCreateCustomerMutationVariables = {
     input: CreateCustomerInput;
 };
@@ -5261,6 +5317,28 @@ export namespace Q2 {
     export type Product = NonNullable<Q2Query['product']>;
 }
 
+export namespace GetAsset {
+    export type Variables = GetAssetQueryVariables;
+    export type Query = GetAssetQuery;
+    export type Asset = AssetFragment;
+}
+
+export namespace CreateAssets {
+    export type Variables = CreateAssetsMutationVariables;
+    export type Mutation = CreateAssetsMutation;
+    export type CreateAssets = AssetFragment;
+    export type FocalPoint = NonNullable<
+        (NonNullable<CreateAssetsMutation['createAssets'][0]>)['focalPoint']
+    >;
+}
+
+export namespace UpdateAsset {
+    export type Variables = UpdateAssetMutationVariables;
+    export type Mutation = UpdateAssetMutation;
+    export type UpdateAsset = AssetFragment;
+    export type FocalPoint = NonNullable<UpdateAssetMutation['updateAsset']['focalPoint']>;
+}
+
 export namespace CanCreateCustomer {
     export type Variables = CanCreateCustomerMutationVariables;
     export type Mutation = CanCreateCustomerMutation;

+ 7 - 0
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -85,6 +85,7 @@ export type Asset = Node & {
     height: Scalars['Int'];
     source: Scalars['String'];
     preview: Scalars['String'];
+    focalPoint?: Maybe<Coordinate>;
 };
 
 export type AssetList = PaginatedList & {
@@ -250,6 +251,12 @@ export type ConfigurableOperationInput = {
     arguments: Array<ConfigArgInput>;
 };
 
+export type Coordinate = {
+    __typename?: 'Coordinate';
+    x: Scalars['Float'];
+    y: Scalars['Float'];
+};
+
 export type Country = Node & {
     __typename?: 'Country';
     id: Scalars['ID'];

+ 9 - 2
packages/core/src/api/resolvers/admin/asset.resolver.ts

@@ -1,9 +1,10 @@
 import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
 import {
-    QueryAssetArgs,
-    QueryAssetsArgs,
     MutationCreateAssetsArgs,
+    MutationUpdateAssetArgs,
     Permission,
+    QueryAssetArgs,
+    QueryAssetsArgs,
 } from '@vendure/common/lib/generated-types';
 import { PaginatedList } from '@vendure/common/lib/shared-types';
 
@@ -39,4 +40,10 @@ export class AssetResolver {
         }
         return assets;
     }
+
+    @Mutation()
+    @Allow(Permission.UpdateCatalog)
+    async updateAsset(@Args() { input }: MutationUpdateAssetArgs) {
+        return this.assetService.update(input);
+    }
 }

+ 14 - 0
packages/core/src/api/schema/admin-api/asset.api.graphql

@@ -1,11 +1,15 @@
 type Query {
+    "Get a list of Assets"
     assets(options: AssetListOptions): AssetList!
+    "Get a single Asset by id"
     asset(id: ID!): Asset
 }
 
 type Mutation {
     "Create a new Asset"
     createAssets(input: [CreateAssetInput!]!): [Asset!]!
+    "Update an existing Asset"
+    updateAsset(input: UpdateAssetInput!): Asset!
 }
 
 # generated by generateListOptions function
@@ -15,3 +19,13 @@ input CreateAssetInput {
     file: Upload!
 }
 
+input CoordinateInput {
+    x: Float!
+    y: Float!
+}
+
+input UpdateAssetInput {
+    id: ID!
+    name: String
+    focalPoint: CoordinateInput
+}

+ 6 - 0
packages/core/src/api/schema/type/asset.type.graphql

@@ -10,6 +10,12 @@ type Asset implements Node {
     height: Int!
     source: String!
     preview: String!
+    focalPoint: Coordinate
+}
+
+type Coordinate {
+    x: Float!
+    y: Float!
 }
 
 type AssetList implements PaginatedList {

+ 3 - 0
packages/core/src/entity/asset/asset.entity.ts

@@ -35,4 +35,7 @@ export class Asset extends VendureEntity {
     @Column() source: string;
 
     @Column() preview: string;
+
+    @Column('simple-json', { nullable: true })
+    focalPoint?: { x: number; y: number };
 }

+ 1 - 1
packages/core/src/service/helpers/utils/patch-entity.ts

@@ -5,7 +5,7 @@ export type InputPatch<T> = { [K in keyof T]?: T[K] | null };
 /**
  * Updates only the specified properties from an Input object as long as the value is not
  * undefined. Null values can be passed, however, which will set the corresponding entity
- * field to "null". So case must be taken that this is only done on nullable fields.
+ * field to "null". So care must be taken that this is only done on nullable fields.
  */
 export function patchEntity<T extends VendureEntity, I extends InputPatch<T>>(entity: T, input: I): T {
     for (const key of Object.keys(entity)) {

+ 11 - 2
packages/core/src/service/services/asset.service.ts

@@ -1,6 +1,6 @@
 import { Injectable } from '@nestjs/common';
 import { InjectConnection } from '@nestjs/typeorm';
-import { AssetType, CreateAssetInput } from '@vendure/common/lib/generated-types';
+import { AssetType, CreateAssetInput, UpdateAssetInput } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList, Type } from '@vendure/common/lib/shared-types';
 import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
 import { ReadStream } from 'fs-extra';
@@ -9,7 +9,7 @@ import path from 'path';
 import { Stream } from 'stream';
 import { Connection, Like } from 'typeorm';
 
-import { InternalServerError } from '../../common/error/errors';
+import { InternalServerError, UserInputError } from '../../common/error/errors';
 import { ListQueryOptions } from '../../common/types/common-types';
 import { getAssetType, idsAreEqual } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
@@ -18,6 +18,8 @@ import { Asset } from '../../entity/asset/asset.entity';
 import { OrderableAsset } from '../../entity/asset/orderable-asset.entity';
 import { VendureEntity } from '../../entity/base/base.entity';
 import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
+import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
+import { patchEntity } from '../helpers/utils/patch-entity';
 // tslint:disable-next-line:no-var-requires
 const sizeOf = require('image-size');
 
@@ -137,6 +139,12 @@ export class AssetService {
         return this.createAssetInternal(stream, filename, mimetype);
     }
 
+    async update(input: UpdateAssetInput): Promise<Asset> {
+        const asset = await getEntityOrThrow(this.connection, Asset, input.id);
+        patchEntity(asset, input);
+        return this.connection.getRepository(Asset).save(asset);
+    }
+
     /**
      * Create an Asset from a file stream created during data import.
      */
@@ -176,6 +184,7 @@ export class AssetService {
             mimeType: mimetype,
             source: sourceFileIdentifier,
             preview: previewFileIdentifier,
+            focalPoint: null,
         });
         return this.connection.manager.save(asset);
     }

+ 2 - 2
packages/dev-server/dev-config.ts

@@ -72,8 +72,8 @@ export const devConfig: VendureConfig = {
         UiPlugin,
         AdminUiPlugin.init({
             port: 5001,
-            extensions: UiPlugin.uiExtensions,
-            watch: true,
+            // extensions: UiPlugin.uiExtensions,
+            // watch: true,
         }),
     ],
 };

+ 26 - 0
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -115,6 +115,7 @@ export type Asset = Node & {
     height: Scalars['Int'];
     source: Scalars['String'];
     preview: Scalars['String'];
+    focalPoint?: Maybe<Coordinate>;
 };
 
 export type AssetFilterParameter = {
@@ -337,6 +338,17 @@ export type ConfigurableOperationInput = {
     arguments: Array<ConfigArgInput>;
 };
 
+export type Coordinate = {
+    __typename?: 'Coordinate';
+    x: Scalars['Float'];
+    y: Scalars['Float'];
+};
+
+export type CoordinateInput = {
+    x: Scalars['Float'];
+    y: Scalars['Float'];
+};
+
 export type Country = Node & {
     __typename?: 'Country';
     id: Scalars['ID'];
@@ -1692,6 +1704,8 @@ export type Mutation = {
     assignRoleToAdministrator: Administrator;
     /** Create a new Asset */
     createAssets: Array<Asset>;
+    /** Update an existing Asset */
+    updateAsset: Asset;
     login: LoginResult;
     logout: Scalars['Boolean'];
     /** Create a new Channel */
@@ -1841,6 +1855,10 @@ export type MutationCreateAssetsArgs = {
     input: Array<CreateAssetInput>;
 };
 
+export type MutationUpdateAssetArgs = {
+    input: UpdateAssetInput;
+};
+
 export type MutationLoginArgs = {
     username: Scalars['String'];
     password: Scalars['String'];
@@ -2660,7 +2678,9 @@ export type Query = {
     __typename?: 'Query';
     administrators: AdministratorList;
     administrator?: Maybe<Administrator>;
+    /** Get a list of Assets */
     assets: AssetList;
+    /** Get a single Asset by id */
     asset?: Maybe<Asset>;
     me?: Maybe<CurrentUser>;
     channels: Array<Channel>;
@@ -3249,6 +3269,12 @@ export type UpdateAdministratorInput = {
     roleIds?: Maybe<Array<Scalars['ID']>>;
 };
 
+export type UpdateAssetInput = {
+    id: Scalars['ID'];
+    name?: Maybe<Scalars['String']>;
+    focalPoint?: Maybe<CoordinateInput>;
+};
+
 export type UpdateChannelInput = {
     id: Scalars['ID'];
     code?: Maybe<Scalars['String']>;

File diff ditekan karena terlalu besar
+ 0 - 0
schema-shop.json


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini