1
0
Эх сурвалжийг харах

chore: Basic setup done with AI agent

David Höck 8 сар өмнө
parent
commit
42d052de28

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 437 - 458
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 653 - 674
packages/common/src/generated-shop-types.ts


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 437 - 458
packages/core/e2e/graphql/generated-e2e-admin-types.ts


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 624 - 645
packages/core/e2e/graphql/generated-e2e-shop-types.ts


+ 2 - 0
packages/core/src/api/api-internal-modules.ts

@@ -24,6 +24,7 @@ import { DraftOrderResolver } from './resolvers/admin/draft-order.resolver';
 import { DuplicateEntityResolver } from './resolvers/admin/duplicate-entity.resolver';
 import { FacetResolver } from './resolvers/admin/facet.resolver';
 import { GlobalSettingsResolver } from './resolvers/admin/global-settings.resolver';
+import { ConfigStorageResolver } from './resolvers/admin/config-storage.resolver';
 import { ImportResolver } from './resolvers/admin/import.resolver';
 import { JobResolver } from './resolvers/admin/job.resolver';
 import { OrderResolver } from './resolvers/admin/order.resolver';
@@ -105,6 +106,7 @@ const adminResolvers = [
     DuplicateEntityResolver,
     FacetResolver,
     GlobalSettingsResolver,
+    ConfigStorageResolver,
     ImportResolver,
     JobResolver,
     OrderResolver,

+ 70 - 0
packages/core/src/api/resolvers/admin/config-storage.resolver.ts

@@ -0,0 +1,70 @@
+import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
+import {
+    DeletionResponse,
+    MutationCreateConfigArgs,
+    MutationDeleteConfigArgs,
+    MutationUpdateConfigArgs,
+    Permission,
+    QueryConfigArgs,
+    QueryConfigsArgs,
+} from '@vendure/common/lib/generated-types';
+
+import { Config } from '../../../entity/config/config.entity';
+import { ConfigStorageService } from '../../../service/services/config-storage.service';
+import { RequestContext } from '../../common/request-context';
+import { Allow } from '../../decorators/allow.decorator';
+import { Ctx } from '../../decorators/request-context.decorator';
+import { Transaction } from '../../decorators/transaction.decorator';
+
+@Resolver('Config')
+export class ConfigStorageResolver {
+    constructor(private configStorageService: ConfigStorageService) {}
+
+    @Query()
+    @Allow(Permission.Authenticated)
+    async configs(
+        @Ctx() ctx: RequestContext,
+        @Args() args: QueryConfigsArgs,
+    ): Promise<Config[]> {
+        return this.configStorageService.getConfigs(ctx);
+    }
+
+    @Query()
+    @Allow(Permission.Authenticated)
+    async config(
+        @Ctx() ctx: RequestContext,
+        @Args() args: QueryConfigArgs,
+    ): Promise<Config | undefined> {
+        return this.configStorageService.getConfig(ctx, args.key);
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.Authenticated)
+    async createConfig(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationCreateConfigArgs,
+    ): Promise<Config> {
+        return this.configStorageService.createConfig(ctx, args.input);
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.Authenticated)
+    async updateConfig(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationUpdateConfigArgs,
+    ): Promise<Config> {
+        return this.configStorageService.updateConfig(ctx, args.input);
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.Authenticated)
+    async deleteConfig(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationDeleteConfigArgs,
+    ): Promise<DeletionResponse> {
+        return this.configStorageService.deleteConfig(ctx, args.key);
+    }
+}

+ 25 - 0
packages/core/src/api/schema/admin-api/config.api.graphql

@@ -0,0 +1,25 @@
+extend type Query {
+  """
+  Retrieve a specific config entry by key.
+  """
+  config(key: String!): Config
+  """
+  Retrieve all config entries.
+  """
+  configs: [Config!]!
+}
+
+extend type Mutation {
+  """
+  Create a new config entry.
+  """
+  createConfig(input: CreateConfigInput!): Config!
+  """
+  Update an existing config entry.
+  """
+  updateConfig(input: UpdateConfigInput!): Config!
+  """
+  Delete a config entry by key.
+  """
+  deleteConfig(key: String!): DeletionResponse!
+}

+ 40 - 0
packages/core/src/api/schema/admin-api/config.type.graphql

@@ -0,0 +1,40 @@
+"""
+Stores a plugin-defined config entry, including its permissions.
+"""
+type Config {
+  id: ID!
+  createdAt: DateTime!
+  updatedAt: DateTime!
+  key: String!
+  plugin: String!
+  value: JSON!
+  createPermissions: [Permission!]!
+  readPermissions: [Permission!]!
+  updatePermissions: [Permission!]!
+  deletePermissions: [Permission!]!
+}
+
+"""
+Input for creating a new Config entry.
+"""
+input CreateConfigInput {
+  key: String!
+  plugin: String!
+  value: JSON!
+  createPermissions: [Permission!]!
+  readPermissions: [Permission!]!
+  updatePermissions: [Permission!]!
+  deletePermissions: [Permission!]!
+}
+
+"""
+Input for updating an existing Config entry.
+"""
+input UpdateConfigInput {
+  key: String!
+  value: JSON
+  createPermissions: [Permission!]
+  readPermissions: [Permission!]
+  updatePermissions: [Permission!]
+  deletePermissions: [Permission!]
+}

+ 60 - 0
packages/core/src/entity/config/config.entity.ts

@@ -0,0 +1,60 @@
+import { Column, Entity } from 'typeorm';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
+import { Permission } from '@vendure/common/lib/generated-types';
+
+import { VendureEntity } from '..';
+
+/**
+ * @description
+ * Stores plugin-defined key-value config entries, with associated CRUD permissions.
+ *
+ * @docsCategory entities
+ */
+@Entity()
+export class Config extends VendureEntity {
+    constructor(input?: DeepPartial<Config>) {
+        super(input);
+    }
+
+    /**
+     * The unique key identifying this config entry.
+     */
+    @Column({ unique: true })
+    key: string;
+
+    /**
+     * The plugin which registered this config entry.
+     */
+    @Column()
+    plugin: string;
+
+    /**
+     * The JSON-stringified value of this config entry.
+     */
+    @Column('text')
+    value: string;
+
+    /**
+     * Permissions required to create this config entry.
+     */
+    @Column('simple-array')
+    createPermissions: Permission[];
+
+    /**
+     * Permissions required to read this config entry.
+     */
+    @Column('simple-array')
+    readPermissions: Permission[];
+
+    /**
+     * Permissions required to update this config entry.
+     */
+    @Column('simple-array')
+    updatePermissions: Permission[];
+
+    /**
+     * Permissions required to delete this config entry.
+     */
+    @Column('simple-array')
+    deletePermissions: Permission[];
+}

+ 1 - 0
packages/core/src/entity/index.ts

@@ -19,6 +19,7 @@ export * from './facet/facet-translation.entity';
 export * from './facet/facet.entity';
 export * from './fulfillment/fulfillment.entity';
 export * from './global-settings/global-settings.entity';
+export * from './config/config.entity';
 export * from './order/order.entity';
 export * from './order-line/order-line.entity';
 export * from './order-line-reference/fulfillment-line.entity';

+ 1 - 0
packages/core/src/service/index.ts

@@ -38,6 +38,7 @@ export * from './services/facet-value.service';
 export * from './services/facet.service';
 export * from './services/fulfillment.service';
 export * from './services/global-settings.service';
+export * from './services/config-storage.service';
 export * from './services/history.service';
 export * from './services/order-testing.service';
 export * from './services/order.service';

+ 3 - 0
packages/core/src/service/initializer.service.ts

@@ -9,6 +9,7 @@ import { InitializerEvent } from '../event-bus/events/initializer-event';
 import { AdministratorService } from './services/administrator.service';
 import { ChannelService } from './services/channel.service';
 import { GlobalSettingsService } from './services/global-settings.service';
+import { ConfigStorageService } from './services/config-storage.service';
 import { RoleService } from './services/role.service';
 import { SellerService } from './services/seller.service';
 import { ShippingMethodService } from './services/shipping-method.service';
@@ -33,6 +34,7 @@ export class InitializerService {
         private administratorService: AdministratorService,
         private shippingMethodService: ShippingMethodService,
         private globalSettingsService: GlobalSettingsService,
+        private configStorageService: ConfigStorageService,
         private taxRateService: TaxRateService,
         private sellerService: SellerService,
         private eventBus: EventBus,
@@ -49,6 +51,7 @@ export class InitializerService {
         // there is a default Channel to work with.
         await this.zoneService.initZones();
         await this.globalSettingsService.initGlobalSettings();
+        await this.configStorageService.initConfigDefinitions();
         await this.sellerService.initSellers();
         await this.channelService.initChannels();
         await this.roleService.initRoles();

+ 2 - 0
packages/core/src/service/service.module.ts

@@ -45,6 +45,7 @@ import { FacetValueService } from './services/facet-value.service';
 import { FacetService } from './services/facet.service';
 import { FulfillmentService } from './services/fulfillment.service';
 import { GlobalSettingsService } from './services/global-settings.service';
+import { ConfigStorageService } from './services/config-storage.service';
 import { HistoryService } from './services/history.service';
 import { OrderTestingService } from './services/order-testing.service';
 import { OrderService } from './services/order.service';
@@ -82,6 +83,7 @@ const services = [
     FacetValueService,
     FulfillmentService,
     GlobalSettingsService,
+    ConfigStorageService,
     HistoryService,
     OrderService,
     OrderTestingService,

+ 203 - 0
packages/core/src/service/services/config-storage.service.ts

@@ -0,0 +1,203 @@
+import { Injectable } from '@nestjs/common';
+import { Instrument } from '../../common/instrument-decorator';
+import { TransactionalConnection } from '../../connection/transactional-connection';
+import { RequestContext } from '../../api/common/request-context';
+import { ForbiddenError, InternalServerError } from '../../common/error/errors';
+import { Config } from '../../entity/config/config.entity';
+import { Permission } from '@vendure/common/lib/generated-types';
+
+/**
+ * @description
+ * Defines a config key registration with default value and CRUD permissions.
+ */
+export interface ConfigKeyDefinition {
+    /** The plugin which owns this config key */
+    plugin: string;
+    /** The unique key identifier */
+    key: string;
+    /** The default value for this config key */
+    defaultValue: any;
+    /** Permissions required for each CRUD operation */
+    permissions: {
+        create: Permission[];
+        read: Permission[];
+        update: Permission[];
+        delete: Permission[];
+    };
+}
+
+/**
+ * @description
+ * A global key-value storage system allowing plugins to register and manage config values.
+ * Config entries and their permissions are persisted in the database.
+ *
+ * @docsCategory services
+ */
+@Injectable()
+@Instrument()
+export class ConfigStorageService {
+    private static definitions: ConfigKeyDefinition[] = [];
+
+    constructor(private connection: TransactionalConnection) {}
+
+    /**
+     * Register a config key definition. Plugins should call this to define keys,
+     * default values, and CRUD permissions.
+     */
+    static registerDefinition(def: ConfigKeyDefinition): void {
+        if (this.definitions.find(d => d.key === def.key)) {
+            throw new Error(`Config key '${def.key}' is already registered`);
+        }
+        this.definitions.push(def);
+    }
+
+    /**
+     * Initializes the config entries in the database for all registered definitions.
+     */
+    async initConfigDefinitions(): Promise<void> {
+        const repo = this.connection.rawConnection.getRepository(Config);
+        for (const def of ConfigStorageService.definitions) {
+            let entity = await repo.findOne({ where: { key: def.key } });
+            if (!entity) {
+                entity = repo.create({
+                    key: def.key,
+                    plugin: def.plugin,
+                    value: JSON.stringify(def.defaultValue),
+                    createPermissions: def.permissions.create,
+                    readPermissions: def.permissions.read,
+                    updatePermissions: def.permissions.update,
+                    deletePermissions: def.permissions.delete,
+                });
+                await repo.save(entity);
+            } else {
+                let changed = false;
+                if (entity.plugin !== def.plugin) {
+                    entity.plugin = def.plugin;
+                    changed = true;
+                }
+                if (!this.arraysEqual(entity.createPermissions, def.permissions.create)) {
+                    entity.createPermissions = def.permissions.create;
+                    changed = true;
+                }
+                if (!this.arraysEqual(entity.readPermissions, def.permissions.read)) {
+                    entity.readPermissions = def.permissions.read;
+                    changed = true;
+                }
+                if (!this.arraysEqual(entity.updatePermissions, def.permissions.update)) {
+                    entity.updatePermissions = def.permissions.update;
+                    changed = true;
+                }
+                if (!this.arraysEqual(entity.deletePermissions, def.permissions.delete)) {
+                    entity.deletePermissions = def.permissions.delete;
+                    changed = true;
+                }
+                if (changed) {
+                    await repo.save(entity);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns all config entries.
+     */
+    async getConfigs(ctx: RequestContext): Promise<Config[]> {
+        return this.connection.getRepository(ctx, Config).find();
+    }
+
+    /**
+     * Returns the config entry for the given key.
+     */
+    async getConfig(ctx: RequestContext, key: string): Promise<Config | undefined> {
+        const entity = await this.connection.getRepository(ctx, Config).findOne({ where: { key } });
+        if (!entity) {
+            return undefined;
+        }
+        this.assertPermission(ctx, entity, 'read');
+        return entity;
+    }
+
+    /**
+     * Creates a new config entry. The key must be registered via registerDefinition().
+     */
+    async createConfig(ctx: RequestContext, input: { key: string; plugin: string; value: any; createPermissions: Permission[]; readPermissions: Permission[]; updatePermissions: Permission[]; deletePermissions: Permission[]; }): Promise<Config> {
+        const def = this.getDefinition(input.key);
+        this.assertPermission(ctx, def, 'create');
+        const repo = this.connection.getRepository(ctx, Config);
+        const exists = await repo.findOne({ where: { key: input.key } });
+        if (exists) {
+            throw new InternalServerError(`Config key '${input.key}' already exists`);
+        }
+        const entity = repo.create({
+            key: input.key,
+            plugin: def.plugin,
+            value: JSON.stringify(input.value),
+            createPermissions: def.permissions.create,
+            readPermissions: def.permissions.read,
+            updatePermissions: def.permissions.update,
+            deletePermissions: def.permissions.delete,
+        });
+        return repo.save(entity);
+    }
+
+    /**
+     * Updates an existing config entry.
+     */
+    async updateConfig(ctx: RequestContext, input: { key: string; value?: any; createPermissions?: Permission[]; readPermissions?: Permission[]; updatePermissions?: Permission[]; deletePermissions?: Permission[]; }): Promise<Config> {
+        const repo = this.connection.getRepository(ctx, Config);
+        const entity = await repo.findOne({ where: { key: input.key } });
+        if (!entity) {
+            throw new InternalServerError(`Config key '${input.key}' not found`);
+        }
+        this.assertPermission(ctx, entity, 'update');
+        if (input.value !== undefined) {
+            entity.value = JSON.stringify(input.value);
+        }
+        if (input.createPermissions) {
+            entity.createPermissions = input.createPermissions;
+        }
+        if (input.readPermissions) {
+            entity.readPermissions = input.readPermissions;
+        }
+        if (input.updatePermissions) {
+            entity.updatePermissions = input.updatePermissions;
+        }
+        if (input.deletePermissions) {
+            entity.deletePermissions = input.deletePermissions;
+        }
+        return repo.save(entity);
+    }
+
+    /**
+     * Deletes the config entry for the given key.
+     */
+    async deleteConfig(ctx: RequestContext, key: string): Promise<{ result: 'DELETED'; message?: string }> {
+        const repo = this.connection.getRepository(ctx, Config);
+        const entity = await repo.findOne({ where: { key } });
+        if (!entity) {
+            throw new InternalServerError(`Config key '${key}' not found`);
+        }
+        this.assertPermission(ctx, entity, 'delete');
+        await repo.remove(entity as any);
+        return { result: 'DELETED' };
+    }
+
+    private getDefinition(key: string): ConfigKeyDefinition {
+        const def = ConfigStorageService.definitions.find(d => d.key === key);
+        if (!def) {
+            throw new InternalServerError(`Config key '${key}' is not registered`);
+        }
+        return def;
+    }
+
+    private assertPermission(ctx: RequestContext, target: ConfigKeyDefinition | Config, operation: keyof ConfigKeyDefinition['permissions']): void {
+        const perms = target instanceof Config ? target[`${operation}Permissions`] : target.permissions[operation];
+        if (perms.length && !perms.some(p => ctx.hasPermission(p))) {
+            throw new ForbiddenError();
+        }
+    }
+
+    private arraysEqual(a: any[], b: any[]): boolean {
+        return a.length === b.length && a.every((v, i) => v === b[i]);
+    }
+}

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 437 - 458
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 383 - 403
packages/payments-plugin/e2e/graphql/generated-admin-types.ts


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 628 - 649
packages/payments-plugin/e2e/graphql/generated-shop-types.ts


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 657 - 678
packages/payments-plugin/src/mollie/graphql/generated-shop-types.ts


Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно