Просмотр исходного кода

feat: Add Country entity & admin-ui list detail components

Michael Bromley 7 лет назад
Родитель
Сommit
ea4d2e27bb
30 измененных файлов с 772 добавлено и 19 удалено
  1. 7 0
      admin-ui/src/app/core/components/main-nav/main-nav.component.html
  2. 49 0
      admin-ui/src/app/data/definitions/settings-definitions.ts
  3. 6 0
      admin-ui/src/app/data/providers/data.service.mock.ts
  4. 3 0
      admin-ui/src/app/data/providers/data.service.ts
  5. 46 0
      admin-ui/src/app/data/providers/settings-data.service.ts
  6. 31 0
      admin-ui/src/app/settings/components/country-detail/country-detail.component.html
  7. 0 0
      admin-ui/src/app/settings/components/country-detail/country-detail.component.scss
  8. 111 0
      admin-ui/src/app/settings/components/country-detail/country-detail.component.ts
  9. 36 0
      admin-ui/src/app/settings/components/country-list/country-list.component.html
  10. 5 0
      admin-ui/src/app/settings/components/country-list/country-list.component.scss
  11. 22 0
      admin-ui/src/app/settings/components/country-list/country-list.component.ts
  12. 24 0
      admin-ui/src/app/settings/providers/routing/country-resolver.ts
  13. 6 1
      admin-ui/src/app/settings/settings.module.ts
  14. 28 0
      admin-ui/src/app/settings/settings.routes.ts
  15. 4 0
      admin-ui/src/i18n-messages/en.json
  16. 0 0
      schema.json
  17. 4 0
      server/e2e/__snapshots__/administrator.e2e-spec.ts.snap
  18. 2 0
      server/src/api/api.module.ts
  19. 45 0
      server/src/api/resolvers/country.resolver.ts
  20. 40 0
      server/src/api/types/country.api.graphql
  21. 5 0
      server/src/common/types/permission.graphql
  22. 17 0
      server/src/entity/country/country.entity.ts
  23. 20 0
      server/src/entity/country/country.graphql
  24. 2 0
      server/src/entity/entities.ts
  25. 0 10
      server/src/service/helpers/connection.decorator.ts
  26. 45 0
      server/src/service/providers/country.service.ts
  27. 1 2
      server/src/service/providers/facet-value.service.ts
  28. 2 2
      server/src/service/providers/role.service.ts
  29. 2 0
      server/src/service/service.module.ts
  30. 209 4
      shared/generated-types.ts

+ 7 - 0
admin-ui/src/app/core/components/main-nav/main-nav.component.html

@@ -82,6 +82,13 @@
                         <clr-icon shape="calculator" size="20"></clr-icon>{{ 'nav.tax-categories' | translate }}
                     </a>
                 </li>
+                <li>
+                    <a class="nav-link"
+                       [routerLink]="['/settings', 'countries']"
+                       routerLinkActive="active">
+                        <clr-icon shape="world" size="20"></clr-icon>{{ 'nav.countries' | translate }}
+                    </a>
+                </li>
             </ul>
         </section>
     </section>

+ 49 - 0
admin-ui/src/app/data/definitions/settings-definitions.ts

@@ -0,0 +1,49 @@
+import gql from 'graphql-tag';
+
+export const COUNTRY_FRAGMENT = gql`
+    fragment Country on Country {
+        id
+        code
+        name
+        enabled
+    }
+`;
+
+export const GET_COUNTRY_LIST = gql`
+    query GetCountryList($options: CountryListOptions) {
+        countries(options: $options) {
+            items {
+                ...Country
+            }
+            totalItems
+        }
+    }
+    ${COUNTRY_FRAGMENT}
+`;
+
+export const GET_COUNTRY = gql`
+    query GetCountry($id: ID!) {
+        country(id: $id) {
+            ...Country
+        }
+    }
+    ${COUNTRY_FRAGMENT}
+`;
+
+export const CREATE_COUNTRY = gql`
+    mutation CreateCountry($input: CreateCountryInput!) {
+        createCountry(input: $input) {
+            ...Country
+        }
+    }
+    ${COUNTRY_FRAGMENT}
+`;
+
+export const UPDATE_COUNTRY = gql`
+    mutation UpdateCountry($input: UpdateCountryInput!) {
+        updateCountry(input: $input) {
+            ...Country
+        }
+    }
+    ${COUNTRY_FRAGMENT}
+`;

+ 6 - 0
admin-ui/src/app/data/providers/data.service.mock.ts

@@ -92,4 +92,10 @@ export class MockDataService implements DataServiceMock {
         createFacetValues: spyObservable('createFacetValues'),
         updateFacetValues: spyObservable('updateFacetValues'),
     };
+    settings = {
+        getCountries: spyQueryResult('getCountries'),
+        getCountry: spyQueryResult('getCountry'),
+        createCountry: spyObservable('createCountry'),
+        updateCountry: spyObservable('updateCountry'),
+    };
 }

+ 3 - 0
admin-ui/src/app/data/providers/data.service.ts

@@ -8,6 +8,7 @@ import { ClientDataService } from './client-data.service';
 import { FacetDataService } from './facet-data.service';
 import { OrderDataService } from './order-data.service';
 import { ProductDataService } from './product-data.service';
+import { SettingsDataService } from './settings-data.service';
 
 @Injectable()
 export class DataService {
@@ -18,6 +19,7 @@ export class DataService {
     client: ClientDataService;
     facet: FacetDataService;
     order: OrderDataService;
+    settings: SettingsDataService;
 
     constructor(baseDataService: BaseDataService) {
         this.adjustmentSource = new AdjustmentSourceDataService(baseDataService);
@@ -27,5 +29,6 @@ export class DataService {
         this.client = new ClientDataService(baseDataService);
         this.facet = new FacetDataService(baseDataService);
         this.order = new OrderDataService(baseDataService);
+        this.settings = new SettingsDataService(baseDataService);
     }
 }

+ 46 - 0
admin-ui/src/app/data/providers/settings-data.service.ts

@@ -0,0 +1,46 @@
+import {
+    CreateCountry,
+    CreateCountryInput,
+    GetCountry,
+    GetCountryList,
+    UpdateCountry,
+    UpdateCountryInput,
+} from 'shared/generated-types';
+
+import {
+    CREATE_COUNTRY,
+    GET_COUNTRY,
+    GET_COUNTRY_LIST,
+    UPDATE_COUNTRY,
+} from '../definitions/settings-definitions';
+
+import { BaseDataService } from './base-data.service';
+
+export class SettingsDataService {
+    constructor(private baseDataService: BaseDataService) {}
+
+    getCountries(take: number = 10, skip: number = 0) {
+        return this.baseDataService.query<GetCountryList.Query, GetCountryList.Variables>(GET_COUNTRY_LIST, {
+            options: {
+                take,
+                skip,
+            },
+        });
+    }
+
+    getCountry(id: string) {
+        return this.baseDataService.query<GetCountry.Query, GetCountry.Variables>(GET_COUNTRY, { id });
+    }
+
+    createCountry(input: CreateCountryInput) {
+        return this.baseDataService.mutate<CreateCountry.Mutation, CreateCountry.Variables>(CREATE_COUNTRY, {
+            input,
+        });
+    }
+
+    updateCountry(input: UpdateCountryInput) {
+        return this.baseDataService.mutate<UpdateCountry.Mutation, UpdateCountry.Variables>(UPDATE_COUNTRY, {
+            input,
+        });
+    }
+}

+ 31 - 0
admin-ui/src/app/settings/components/country-detail/country-detail.component.html

@@ -0,0 +1,31 @@
+<vdr-action-bar>
+    <vdr-ab-left></vdr-ab-left>
+    <vdr-ab-right>
+        <button class="btn btn-primary"
+                *ngIf="isNew$ | async; else updateButton"
+                (click)="create()"
+                [disabled]="countryForm.invalid || countryForm.pristine">{{ 'common.create' | translate }}</button>
+        <ng-template #updateButton>
+            <button class="btn btn-primary"
+                    (click)="save()"
+                    [disabled]="countryForm.invalid || countryForm.pristine">{{ 'common.update' | translate }}</button>
+        </ng-template>
+    </vdr-ab-right>
+</vdr-action-bar>
+
+<form class="form" [formGroup]="countryForm" >
+    <section class="form-block">
+        <vdr-form-field [label]="'common.code' | translate" for="code">
+            <input id="code" type="text" formControlName="code">
+        </vdr-form-field>
+        <vdr-form-field [label]="'common.name' | translate" for="name">
+            <input id="name" type="text" formControlName="name">
+        </vdr-form-field>
+        <vdr-form-field [label]="'common.enabled' | translate" for="enabled">
+            <div class="toggle-switch">
+                <input type="checkbox" id="enabled" formControlName="enabled">
+                <label for="enabled"></label>
+            </div>
+        </vdr-form-field>
+    </section>
+</form>

+ 0 - 0
admin-ui/src/app/settings/components/country-detail/country-detail.component.scss


+ 111 - 0
admin-ui/src/app/settings/components/country-detail/country-detail.component.ts

@@ -0,0 +1,111 @@
+import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
+import { Observable } from 'rxjs';
+import { mergeMap, take } from 'rxjs/operators';
+import { Country, CreateCountryInput, UpdateCountryInput } from 'shared/generated-types';
+
+import { BaseDetailComponent } from '../../../common/base-detail.component';
+import { _ } from '../../../core/providers/i18n/mark-for-extraction';
+import { NotificationService } from '../../../core/providers/notification/notification.service';
+import { DataService } from '../../../data/providers/data.service';
+import { ServerConfigService } from '../../../data/server-config';
+
+@Component({
+    selector: 'vdr-country-detail',
+    templateUrl: './country-detail.component.html',
+    styleUrls: ['./country-detail.component.scss'],
+})
+export class CountryDetailComponent extends BaseDetailComponent<Country> implements OnInit, OnDestroy {
+    country$: Observable<Country>;
+    countryForm: FormGroup;
+
+    constructor(
+        router: Router,
+        route: ActivatedRoute,
+        serverConfigService: ServerConfigService,
+        private changeDetector: ChangeDetectorRef,
+        private dataService: DataService,
+        private formBuilder: FormBuilder,
+        private notificationService: NotificationService,
+    ) {
+        super(route, router, serverConfigService);
+        this.countryForm = this.formBuilder.group({
+            code: ['', Validators.required],
+            name: ['', Validators.required],
+            enabled: [true],
+        });
+    }
+
+    ngOnInit() {
+        this.init();
+        this.country$ = this.entity$;
+    }
+
+    ngOnDestroy(): void {
+        this.destroy();
+    }
+
+    create() {
+        const formValue = this.countryForm.value;
+        const country: CreateCountryInput = {
+            code: formValue.code,
+            name: formValue.name,
+            enabled: formValue.enabled,
+        };
+        this.dataService.settings.createCountry(country).subscribe(
+            data => {
+                this.notificationService.success(_('common.notify-create-success'), {
+                    entity: 'Country',
+                });
+                this.countryForm.markAsPristine();
+                this.changeDetector.markForCheck();
+                this.router.navigate(['../', data.createCountry.id], { relativeTo: this.route });
+            },
+            err => {
+                this.notificationService.error(_('common.notify-create-error'), {
+                    entity: 'Country',
+                });
+            },
+        );
+    }
+
+    save() {
+        this.country$
+            .pipe(
+                take(1),
+                mergeMap(({ id }) => {
+                    const formValue = this.countryForm.value;
+                    const country: UpdateCountryInput = {
+                        id,
+                        code: formValue.code,
+                        name: formValue.name,
+                        enabled: formValue.enabled,
+                    };
+                    return this.dataService.settings.updateCountry(country);
+                }),
+            )
+            .subscribe(
+                data => {
+                    this.notificationService.success(_('common.notify-update-success'), {
+                        entity: 'Country',
+                    });
+                    this.countryForm.markAsPristine();
+                    this.changeDetector.markForCheck();
+                },
+                err => {
+                    this.notificationService.error(_('common.notify-update-error'), {
+                        entity: 'Country',
+                    });
+                },
+            );
+    }
+
+    protected setFormValues(country: Country): void {
+        this.countryForm.patchValue({
+            code: country.code,
+            name: country.name,
+            enabled: country.enabled,
+        });
+    }
+}

+ 36 - 0
admin-ui/src/app/settings/components/country-list/country-list.component.html

@@ -0,0 +1,36 @@
+<vdr-action-bar>
+    <vdr-ab-right>
+        <a class="btn btn-primary" [routerLink]="['./create']">
+            <clr-icon shape="plus"></clr-icon>
+            {{ 'settings.create-new-country' | translate }}
+        </a>
+    </vdr-ab-right>
+</vdr-action-bar>
+
+<vdr-data-table [items]="items$ | async"
+                [itemsPerPage]="itemsPerPage$ | async"
+                [totalItems]="totalItems$ | async"
+                [currentPage]="currentPage$ | async"
+                (pageChange)="setPageNumber($event)"
+                (itemsPerPageChange)="setItemsPerPage($event)">
+    <vdr-dt-column>{{ 'common.ID' | translate }}</vdr-dt-column>
+    <vdr-dt-column>{{ 'common.code' | translate }}</vdr-dt-column>
+    <vdr-dt-column>{{ 'common.name' | translate }}</vdr-dt-column>
+    <vdr-dt-column>{{ 'common.enabled' | translate }}</vdr-dt-column>
+    <vdr-dt-column></vdr-dt-column>
+    <ng-template let-country="item">
+        <td class="left">{{ country.id }}</td>
+        <td class="left">{{ country.code }}</td>
+        <td class="left">{{ country.name }}</td>
+        <td class="left">
+            <clr-icon [class.enabled]="country.enabled"
+                      [attr.shape]="country.enabled ? 'check' : 'times'"></clr-icon>
+        </td>
+        <td class="right">
+            <vdr-table-row-action iconShape="edit"
+                                  [label]="'common.edit' | translate"
+                                  [linkTo]="['./', country.id]">
+            </vdr-table-row-action>
+        </td>
+    </ng-template>
+</vdr-data-table>

+ 5 - 0
admin-ui/src/app/settings/components/country-list/country-list.component.scss

@@ -0,0 +1,5 @@
+@import "variables";
+
+clr-icon.enabled {
+    color: $color-success;
+}

+ 22 - 0
admin-ui/src/app/settings/components/country-list/country-list.component.ts

@@ -0,0 +1,22 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { GetCountryList } from 'shared/generated-types';
+
+import { BaseListComponent } from '../../../common/base-list.component';
+import { DataService } from '../../../data/providers/data.service';
+
+@Component({
+    selector: 'vdr-country-list',
+    templateUrl: './country-list.component.html',
+    styleUrls: ['./country-list.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class CountryListComponent extends BaseListComponent<GetCountryList.Query, GetCountryList.Items> {
+    constructor(private dataService: DataService, router: Router, route: ActivatedRoute) {
+        super(router, route);
+        super.setQueryFn(
+            (...args: any[]) => this.dataService.settings.getCountries(...args),
+            data => data.countries,
+        );
+    }
+}

+ 24 - 0
admin-ui/src/app/settings/providers/routing/country-resolver.ts

@@ -0,0 +1,24 @@
+import { Injectable } from '@angular/core';
+import { AdjustmentSource, AdjustmentType, Country } from 'shared/generated-types';
+
+import { BaseEntityResolver } from '../../../common/base-entity-resolver';
+import { DataService } from '../../../data/providers/data.service';
+
+/**
+ * Resolves the id from the path into a Customer entity.
+ */
+@Injectable()
+export class CountryResolver extends BaseEntityResolver<Country.Fragment> {
+    constructor(private dataService: DataService) {
+        super(
+            {
+                __typename: 'Country',
+                id: '',
+                code: '',
+                name: '',
+                enabled: false,
+            },
+            id => this.dataService.settings.getCountry(id).mapStream(data => data.country),
+        );
+    }
+}

+ 6 - 1
admin-ui/src/app/settings/settings.module.ts

@@ -5,12 +5,15 @@ import { SharedModule } from '../shared/shared.module';
 
 import { AdminDetailComponent } from './components/admin-detail/admin-detail.component';
 import { AdministratorListComponent } from './components/administrator-list/administrator-list.component';
+import { CountryDetailComponent } from './components/country-detail/country-detail.component';
+import { CountryListComponent } from './components/country-list/country-list.component';
 import { PermissionGridComponent } from './components/permission-grid/permission-grid.component';
 import { RoleDetailComponent } from './components/role-detail/role-detail.component';
 import { RoleListComponent } from './components/role-list/role-list.component';
 import { TaxCategoryDetailComponent } from './components/tax-category-detail/tax-category-detail.component';
 import { TaxCategoryListComponent } from './components/tax-category-list/tax-category-list.component';
 import { AdministratorResolver } from './providers/routing/administrator-resolver';
+import { CountryResolver } from './providers/routing/country-resolver';
 import { RoleResolver } from './providers/routing/role-resolver';
 import { TaxCategoryResolver } from './providers/routing/tax-category-resolver';
 import { settingsRoutes } from './settings.routes';
@@ -25,7 +28,9 @@ import { settingsRoutes } from './settings.routes';
         RoleDetailComponent,
         AdminDetailComponent,
         PermissionGridComponent,
+        CountryListComponent,
+        CountryDetailComponent,
     ],
-    providers: [TaxCategoryResolver, AdministratorResolver, RoleResolver],
+    providers: [TaxCategoryResolver, AdministratorResolver, RoleResolver, CountryResolver],
 })
 export class SettingsModule {}

+ 28 - 0
admin-ui/src/app/settings/settings.routes.ts

@@ -7,11 +7,14 @@ import { _ } from '../core/providers/i18n/mark-for-extraction';
 
 import { AdminDetailComponent } from './components/admin-detail/admin-detail.component';
 import { AdministratorListComponent } from './components/administrator-list/administrator-list.component';
+import { CountryDetailComponent } from './components/country-detail/country-detail.component';
+import { CountryListComponent } from './components/country-list/country-list.component';
 import { RoleDetailComponent } from './components/role-detail/role-detail.component';
 import { RoleListComponent } from './components/role-list/role-list.component';
 import { TaxCategoryDetailComponent } from './components/tax-category-detail/tax-category-detail.component';
 import { TaxCategoryListComponent } from './components/tax-category-list/tax-category-list.component';
 import { AdministratorResolver } from './providers/routing/administrator-resolver';
+import { CountryResolver } from './providers/routing/country-resolver';
 import { RoleResolver } from './providers/routing/role-resolver';
 import { TaxCategoryResolver } from './providers/routing/tax-category-resolver';
 
@@ -57,6 +60,21 @@ export const settingsRoutes: Route[] = [
             breadcrumb: taxCategoryBreadcrumb,
         },
     },
+    {
+        path: 'countries',
+        component: CountryListComponent,
+        data: {
+            breadcrumb: _('breadcrumb.countries'),
+        },
+    },
+    {
+        path: 'countries/:id',
+        component: CountryDetailComponent,
+        resolve: createResolveData(CountryResolver),
+        data: {
+            breadcrumb: countryBreadcrumb,
+        },
+    },
 ];
 
 export function administratorBreadcrumb(data: any, params: any) {
@@ -88,3 +106,13 @@ export function taxCategoryBreadcrumb(data: any, params: any) {
         route: 'tax-categories',
     });
 }
+
+export function countryBreadcrumb(data: any, params: any) {
+    return detailBreadcrumb<AdjustmentSource.Fragment>({
+        entity: data.entity,
+        id: params.id,
+        breadcrumbKey: 'breadcrumb.countries',
+        getName: promotion => promotion.name,
+        route: 'countries',
+    });
+}

+ 4 - 0
admin-ui/src/i18n-messages/en.json

@@ -5,6 +5,7 @@
   "breadcrumb": {
     "administrators": "Administrators",
     "assets": "Assets",
+    "countries": "Countries",
     "dashboard": "Dashboard",
     "facets": "Facets",
     "orders": "Orders",
@@ -73,6 +74,7 @@
     "created-at": "Created at",
     "edit": "Edit",
     "edit-field": "Edit field",
+    "enabled": "Enabled",
     "finish": "Finish",
     "items-per-page-option": "{ count } per page",
     "language": "Language",
@@ -111,6 +113,7 @@
     "assets": "Assets",
     "catalog": "Catalog",
     "categories": "Categories",
+    "countries": "Countries",
     "facets": "Facets",
     "marketing": "Marketing",
     "orders": "Orders",
@@ -128,6 +131,7 @@
     "administrator": "Administrator",
     "catalog": "Catalog",
     "create": "Create",
+    "create-new-country": "Create new country",
     "create-new-role": "Create new role",
     "create-new-tax-category": "Create tax category",
     "customer": "Customer",

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
schema.json


+ 4 - 0
server/e2e/__snapshots__/administrator.e2e-spec.ts.snap

@@ -40,6 +40,10 @@ Object {
           "ReadAdjustmentSource",
           "UpdateAdjustmentSource",
           "DeleteAdjustmentSource",
+          "CreateSettings",
+          "ReadSettings",
+          "UpdateSettings",
+          "DeleteSettings",
         ],
       },
     ],

+ 2 - 0
server/src/api/api.module.ts

@@ -17,6 +17,7 @@ import { AssetResolver } from './resolvers/asset.resolver';
 import { AuthResolver } from './resolvers/auth.resolver';
 import { ChannelResolver } from './resolvers/channel.resolver';
 import { ConfigResolver } from './resolvers/config.resolver';
+import { CountryResolver } from './resolvers/country.resolver';
 import { CustomerResolver } from './resolvers/customer.resolver';
 import { FacetResolver } from './resolvers/facet.resolver';
 import { OrderResolver } from './resolvers/order.resolver';
@@ -31,6 +32,7 @@ const exportedProviders = [
     AssetResolver,
     ChannelResolver,
     ConfigResolver,
+    CountryResolver,
     FacetResolver,
     CustomerResolver,
     OrderResolver,

+ 45 - 0
server/src/api/resolvers/country.resolver.ts

@@ -0,0 +1,45 @@
+import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
+import {
+    CountriesQueryArgs,
+    CountryQueryArgs,
+    CreateCountryMutationArgs,
+    Permission,
+    UpdateCountryMutationArgs,
+} from 'shared/generated-types';
+import { PaginatedList } from 'shared/shared-types';
+
+import { Country } from '../../entity/country/country.entity';
+import { Facet } from '../../entity/facet/facet.entity';
+import { CountryService } from '../../service/providers/country.service';
+import { Allow } from '../common/auth-guard';
+import { RequestContext } from '../common/request-context';
+import { Ctx } from '../common/request-context.decorator';
+
+@Resolver('Facet')
+export class CountryResolver {
+    constructor(private countryService: CountryService) {}
+
+    @Query()
+    @Allow(Permission.ReadCatalog)
+    countries(@Ctx() ctx: RequestContext, @Args() args: CountriesQueryArgs): Promise<PaginatedList<Country>> {
+        return this.countryService.findAll(args.options || undefined);
+    }
+
+    @Query()
+    @Allow(Permission.ReadCatalog)
+    async country(@Ctx() ctx: RequestContext, @Args() args: CountryQueryArgs): Promise<Country | undefined> {
+        return this.countryService.findOne(args.id);
+    }
+
+    @Mutation()
+    @Allow(Permission.CreateCatalog)
+    async createCountry(@Args() args: CreateCountryMutationArgs): Promise<Country> {
+        return this.countryService.create(args.input);
+    }
+
+    @Mutation()
+    @Allow(Permission.UpdateCatalog)
+    async updateCountry(@Args() args: UpdateCountryMutationArgs): Promise<Country> {
+        return this.countryService.update(args.input);
+    }
+}

+ 40 - 0
server/src/api/types/country.api.graphql

@@ -0,0 +1,40 @@
+type Query {
+  countries(options: CountryListOptions): CountryList!
+  country(id: ID!): Country
+}
+
+type Mutation {
+  "Create a new Country"
+  createCountry(input: CreateCountryInput!): Country!
+  "Update an existing Country"
+  updateCountry(input: UpdateCountryInput!): Country!
+}
+
+type CountryList implements PaginatedList {
+    items: [Country!]!
+    totalItems: Int!
+}
+
+input CountryListOptions {
+    take: Int
+    skip: Int
+    sort: CountrySortParameter
+    filter: CountryFilterParameter
+}
+
+input CountrySortParameter {
+    id: SortOrder
+    createdAt: SortOrder
+    updatedAt: SortOrder
+    code: SortOrder
+    name: SortOrder
+    enabled: SortOrder
+}
+
+input CountryFilterParameter {
+    code: StringOperators
+    name: StringOperators
+    enabled: BooleanOperators
+    createdAt: DateOperators
+    updatedAt: DateOperators
+}

+ 5 - 0
server/src/common/types/permission.graphql

@@ -33,4 +33,9 @@ enum Permission {
     ReadAdjustmentSource
     UpdateAdjustmentSource
     DeleteAdjustmentSource
+
+    CreateSettings
+    ReadSettings
+    UpdateSettings
+    DeleteSettings
 }

+ 17 - 0
server/src/entity/country/country.entity.ts

@@ -0,0 +1,17 @@
+import { DeepPartial } from 'shared/shared-types';
+import { Column, Entity } from 'typeorm';
+
+import { VendureEntity } from '../base/base.entity';
+
+@Entity()
+export class Country extends VendureEntity {
+    constructor(input?: DeepPartial<Country>) {
+        super(input);
+    }
+
+    @Column() code: string;
+
+    @Column() name: string;
+
+    @Column() enabled: boolean;
+}

+ 20 - 0
server/src/entity/country/country.graphql

@@ -0,0 +1,20 @@
+type Country implements Node {
+    id: ID!
+    code: String!
+    name: String!
+    enabled: Boolean!
+}
+
+input CreateCountryInput {
+    code: String!
+    name: String!
+    enabled: Boolean!
+}
+
+
+input UpdateCountryInput {
+    id: ID!
+    code: String
+    name: String
+    enabled: Boolean
+}

+ 2 - 0
server/src/entity/entities.ts

@@ -3,6 +3,7 @@ import { AdjustmentSource } from './adjustment-source/adjustment-source.entity';
 import { Administrator } from './administrator/administrator.entity';
 import { Asset } from './asset/asset.entity';
 import { Channel } from './channel/channel.entity';
+import { Country } from './country/country.entity';
 import { Customer } from './customer/customer.entity';
 import { FacetValueTranslation } from './facet-value/facet-value-translation.entity';
 import { FacetValue } from './facet-value/facet-value.entity';
@@ -36,6 +37,7 @@ export const coreEntitiesMap = {
     Asset,
     AuthenticatedSession,
     Channel,
+    Country,
     Customer,
     Facet,
     FacetTranslation,

+ 0 - 10
server/src/service/helpers/connection.decorator.ts

@@ -1,10 +0,0 @@
-import { InjectConnection } from '@nestjs/typeorm';
-import { getConnectionManager } from 'typeorm';
-
-import { getConfig } from '../../config/vendure-config';
-
-// TODO: Should be ok to remove this and just use @InjectConnection
-export function ActiveConnection() {
-    const cm = getConnectionManager();
-    return InjectConnection(getConfig().dbConnectionOptions.name);
-}

+ 45 - 0
server/src/service/providers/country.service.ts

@@ -0,0 +1,45 @@
+import { Injectable } from '@nestjs/common';
+import { InjectConnection } from '@nestjs/typeorm';
+import { CreateCountryInput, UpdateCountryInput } from 'shared/generated-types';
+import { ID, PaginatedList } from 'shared/shared-types';
+import { Connection } from 'typeorm';
+
+import { ListQueryOptions } from '../../common/types/common-types';
+import { assertFound } from '../../common/utils';
+import { Country } from '../../entity/country/country.entity';
+import { I18nError } from '../../i18n/i18n-error';
+import { buildListQuery } from '../helpers/build-list-query';
+import { patchEntity } from '../helpers/patch-entity';
+
+@Injectable()
+export class CountryService {
+    constructor(@InjectConnection() private connection: Connection) {}
+
+    findAll(options?: ListQueryOptions<Country>): Promise<PaginatedList<Country>> {
+        return buildListQuery(this.connection, Country, options)
+            .getManyAndCount()
+            .then(([items, totalItems]) => ({
+                items,
+                totalItems,
+            }));
+    }
+
+    findOne(countryId: ID): Promise<Country | undefined> {
+        return this.connection.manager.findOne(Country, countryId);
+    }
+
+    async create(input: CreateCountryInput): Promise<Country> {
+        const country = new Country(input);
+        return this.connection.manager.save(country);
+    }
+
+    async update(input: UpdateCountryInput): Promise<Country> {
+        const country = await this.findOne(input.id);
+        if (!country) {
+            throw new I18nError(`error.entity-with-id-not-found`, { entityName: 'Country', id: input.id });
+        }
+        const updatedCountry = patchEntity(country, input);
+        await this.connection.manager.save(updatedCountry);
+        return assertFound(this.findOne(country.id));
+    }
+}

+ 1 - 2
server/src/service/providers/facet-value.service.ts

@@ -16,7 +16,6 @@ import { FacetValueTranslation } from '../../entity/facet-value/facet-value-tran
 import { FacetValue } from '../../entity/facet-value/facet-value.entity';
 import { Facet } from '../../entity/facet/facet.entity';
 
-import { ActiveConnection } from '../helpers/connection.decorator';
 import { createTranslatable } from '../helpers/create-translatable';
 import { translateDeep } from '../helpers/translate-entity';
 import { TranslationUpdaterService } from '../helpers/translation-updater.service';
@@ -25,7 +24,7 @@ import { updateTranslatable } from '../helpers/update-translatable';
 @Injectable()
 export class FacetValueService {
     constructor(
-        @ActiveConnection() private connection: Connection,
+        @InjectConnection() private connection: Connection,
         private translationUpdaterService: TranslationUpdaterService,
     ) {}
 

+ 2 - 2
server/src/service/providers/role.service.ts

@@ -1,4 +1,5 @@
 import { Injectable } from '@nestjs/common';
+import { InjectConnection } from '@nestjs/typeorm';
 import { CreateRoleInput, Permission, UpdateRoleInput } from 'shared/generated-types';
 import {
     CUSTOMER_ROLE_CODE,
@@ -14,14 +15,13 @@ import { assertFound } from '../../common/utils';
 import { Role } from '../../entity/role/role.entity';
 import { I18nError } from '../../i18n/i18n-error';
 import { buildListQuery } from '../helpers/build-list-query';
-import { ActiveConnection } from '../helpers/connection.decorator';
 import { patchEntity } from '../helpers/patch-entity';
 
 import { ChannelService } from './channel.service';
 
 @Injectable()
 export class RoleService {
-    constructor(@ActiveConnection() private connection: Connection, private channelService: ChannelService) {}
+    constructor(@InjectConnection() private connection: Connection, private channelService: ChannelService) {}
 
     async initRoles() {
         await this.ensureSuperAdminRoleExists();

+ 2 - 0
server/src/service/service.module.ts

@@ -11,6 +11,7 @@ import { AdministratorService } from './providers/administrator.service';
 import { AssetService } from './providers/asset.service';
 import { AuthService } from './providers/auth.service';
 import { ChannelService } from './providers/channel.service';
+import { CountryService } from './providers/country.service';
 import { CustomerService } from './providers/customer.service';
 import { FacetValueService } from './providers/facet-value.service';
 import { FacetService } from './providers/facet.service';
@@ -28,6 +29,7 @@ const exportedProviders = [
     AssetService,
     AuthService,
     ChannelService,
+    CountryService,
     CustomerService,
     FacetService,
     FacetValueService,

+ 209 - 4
shared/generated-types.ts

@@ -48,6 +48,8 @@ export interface Query {
     asset?: Asset | null;
     me?: CurrentUser | null;
     config: Config;
+    countries: CountryList;
+    country?: Country | null;
     customers: CustomerList;
     customer?: Customer | null;
     facets: FacetList;
@@ -167,6 +169,18 @@ export interface Config {
     customFields?: Json | null;
 }
 
+export interface CountryList extends PaginatedList {
+    items: Country[];
+    totalItems: number;
+}
+
+export interface Country extends Node {
+    id: string;
+    code: string;
+    name: string;
+    enabled: boolean;
+}
+
 export interface CustomerList extends PaginatedList {
     items: Customer[];
     totalItems: number;
@@ -431,6 +445,8 @@ export interface Mutation {
     login: LoginResult;
     logout: boolean;
     createChannel: Channel;
+    createCountry: Country;
+    updateCountry: Country;
     createCustomer: Customer;
     createCustomerAddress: Address;
     createFacet: Facet;
@@ -547,6 +563,34 @@ export interface AssetFilterParameter {
     updatedAt?: DateOperators | null;
 }
 
+export interface CountryListOptions {
+    take?: number | null;
+    skip?: number | null;
+    sort?: CountrySortParameter | null;
+    filter?: CountryFilterParameter | null;
+}
+
+export interface CountrySortParameter {
+    id?: SortOrder | null;
+    createdAt?: SortOrder | null;
+    updatedAt?: SortOrder | null;
+    code?: SortOrder | null;
+    name?: SortOrder | null;
+    enabled?: SortOrder | null;
+}
+
+export interface CountryFilterParameter {
+    code?: StringOperators | null;
+    name?: StringOperators | null;
+    enabled?: BooleanOperators | null;
+    createdAt?: DateOperators | null;
+    updatedAt?: DateOperators | null;
+}
+
+export interface BooleanOperators {
+    eq?: boolean | null;
+}
+
 export interface CustomerListOptions {
     take?: number | null;
     skip?: number | null;
@@ -597,10 +641,6 @@ export interface FacetFilterParameter {
     searchable?: BooleanOperators | null;
 }
 
-export interface BooleanOperators {
-    eq?: boolean | null;
-}
-
 export interface OrderListOptions {
     take?: number | null;
     skip?: number | null;
@@ -716,6 +756,19 @@ export interface CreateAssetInput {
     file: Upload;
 }
 
+export interface CreateCountryInput {
+    code: string;
+    name: string;
+    enabled: boolean;
+}
+
+export interface UpdateCountryInput {
+    id: string;
+    code?: string | null;
+    name?: string | null;
+    enabled?: boolean | null;
+}
+
 export interface CreateCustomerInput {
     firstName: string;
     lastName: string;
@@ -951,6 +1004,12 @@ export interface AssetsQueryArgs {
 export interface AssetQueryArgs {
     id: string;
 }
+export interface CountriesQueryArgs {
+    options?: CountryListOptions | null;
+}
+export interface CountryQueryArgs {
+    id: string;
+}
 export interface CustomersQueryArgs {
     options?: CustomerListOptions | null;
 }
@@ -1020,6 +1079,12 @@ export interface LoginMutationArgs {
 export interface CreateChannelMutationArgs {
     code: string;
 }
+export interface CreateCountryMutationArgs {
+    input: CreateCountryInput;
+}
+export interface UpdateCountryMutationArgs {
+    input: UpdateCountryInput;
+}
 export interface CreateCustomerMutationArgs {
     input: CreateCustomerInput;
     password?: string | null;
@@ -1134,6 +1199,10 @@ export enum Permission {
     ReadAdjustmentSource = 'ReadAdjustmentSource',
     UpdateAdjustmentSource = 'UpdateAdjustmentSource',
     DeleteAdjustmentSource = 'DeleteAdjustmentSource',
+    CreateSettings = 'CreateSettings',
+    ReadSettings = 'ReadSettings',
+    UpdateSettings = 'UpdateSettings',
+    DeleteSettings = 'DeleteSettings',
 }
 
 export enum AssetType {
@@ -1340,6 +1409,8 @@ export namespace QueryResolvers {
         asset?: AssetResolver<Asset | null, any, Context>;
         me?: MeResolver<CurrentUser | null, any, Context>;
         config?: ConfigResolver<Config, any, Context>;
+        countries?: CountriesResolver<CountryList, any, Context>;
+        country?: CountryResolver<Country | null, any, Context>;
         customers?: CustomersResolver<CustomerList, any, Context>;
         customer?: CustomerResolver<Customer | null, any, Context>;
         facets?: FacetsResolver<FacetList, any, Context>;
@@ -1434,6 +1505,26 @@ export namespace QueryResolvers {
         Context
     >;
     export type ConfigResolver<R = Config, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type CountriesResolver<R = CountryList, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context,
+        CountriesArgs
+    >;
+    export interface CountriesArgs {
+        options?: CountryListOptions | null;
+    }
+
+    export type CountryResolver<R = Country | null, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context,
+        CountryArgs
+    >;
+    export interface CountryArgs {
+        id: string;
+    }
+
     export type CustomersResolver<R = CustomerList, Parent = any, Context = any> = Resolver<
         R,
         Parent,
@@ -1823,6 +1914,30 @@ export namespace ConfigResolvers {
     >;
 }
 
+export namespace CountryListResolvers {
+    export interface Resolvers<Context = any> {
+        items?: ItemsResolver<Country[], any, Context>;
+        totalItems?: TotalItemsResolver<number, any, Context>;
+    }
+
+    export type ItemsResolver<R = Country[], Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type TotalItemsResolver<R = number, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+}
+
+export namespace CountryResolvers {
+    export interface Resolvers<Context = any> {
+        id?: IdResolver<string, any, Context>;
+        code?: CodeResolver<string, any, Context>;
+        name?: NameResolver<string, any, Context>;
+        enabled?: EnabledResolver<boolean, any, Context>;
+    }
+
+    export type IdResolver<R = string, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type CodeResolver<R = string, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type NameResolver<R = string, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+    export type EnabledResolver<R = boolean, Parent = any, Context = any> = Resolver<R, Parent, Context>;
+}
+
 export namespace CustomerListResolvers {
     export interface Resolvers<Context = any> {
         items?: ItemsResolver<Customer[], any, Context>;
@@ -2586,6 +2701,8 @@ export namespace MutationResolvers {
         login?: LoginResolver<LoginResult, any, Context>;
         logout?: LogoutResolver<boolean, any, Context>;
         createChannel?: CreateChannelResolver<Channel, any, Context>;
+        createCountry?: CreateCountryResolver<Country, any, Context>;
+        updateCountry?: UpdateCountryResolver<Country, any, Context>;
         createCustomer?: CreateCustomerResolver<Customer, any, Context>;
         createCustomerAddress?: CreateCustomerAddressResolver<Address, any, Context>;
         createFacet?: CreateFacetResolver<Facet, any, Context>;
@@ -2701,6 +2818,26 @@ export namespace MutationResolvers {
         code: string;
     }
 
+    export type CreateCountryResolver<R = Country, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context,
+        CreateCountryArgs
+    >;
+    export interface CreateCountryArgs {
+        input: CreateCountryInput;
+    }
+
+    export type UpdateCountryResolver<R = Country, Parent = any, Context = any> = Resolver<
+        R,
+        Parent,
+        Context,
+        UpdateCountryArgs
+    >;
+    export interface UpdateCountryArgs {
+        input: UpdateCountryInput;
+    }
+
     export type CreateCustomerResolver<R = Customer, Parent = any, Context = any> = Resolver<
         R,
         Parent,
@@ -3675,6 +3812,64 @@ export namespace CreateAssets {
     export type CreateAssets = Asset.Fragment;
 }
 
+export namespace GetCountryList {
+    export type Variables = {
+        options?: CountryListOptions | null;
+    };
+
+    export type Query = {
+        __typename?: 'Query';
+        countries: Countries;
+    };
+
+    export type Countries = {
+        __typename?: 'CountryList';
+        items: Items[];
+        totalItems: number;
+    };
+
+    export type Items = Country.Fragment;
+}
+
+export namespace GetCountry {
+    export type Variables = {
+        id: string;
+    };
+
+    export type Query = {
+        __typename?: 'Query';
+        country?: Country | null;
+    };
+
+    export type Country = Country.Fragment;
+}
+
+export namespace CreateCountry {
+    export type Variables = {
+        input: CreateCountryInput;
+    };
+
+    export type Mutation = {
+        __typename?: 'Mutation';
+        createCountry: CreateCountry;
+    };
+
+    export type CreateCountry = Country.Fragment;
+}
+
+export namespace UpdateCountry {
+    export type Variables = {
+        input: UpdateCountryInput;
+    };
+
+    export type Mutation = {
+        __typename?: 'Mutation';
+        updateCountry: UpdateCountry;
+    };
+
+    export type UpdateCountry = Country.Fragment;
+}
+
 export namespace AdjustmentOperation {
     export type Fragment = {
         __typename?: 'AdjustmentOperation';
@@ -3946,3 +4141,13 @@ export namespace ProductOptionGroup {
         name: string;
     };
 }
+
+export namespace Country {
+    export type Fragment = {
+        __typename?: 'Country';
+        id: string;
+        code: string;
+        name: string;
+        enabled: boolean;
+    };
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов