Passed
Push — trunk ( e2a4ed...33293d )
by Christian
11:28 queued 12s
created

ShopwareExtensionService   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 237
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 184
dl 0
loc 237
rs 9.52
c 0
b 0
f 0
wmc 36

21 Functions

Rating   Name   Duplication   Size   Complexity  
A getAppFromStore 0 4 1
A orderVariantsByRecommendation 0 8 1
A isVariantDiscounted 0 10 2
A checkLogin 0 7 2
B getOpenLink 0 27 7
A activateExtension 0 5 1
A appHasMainModule 0 3 2
A uninstallExtension 0 5 1
A updateExtension 0 5 1
A updateExtensionData 0 15 2
A orderByType 0 12 1
A installExtension 0 5 1
A getLinkToTheme 0 18 2
A updateModules 0 5 1
A createLinkToModule 0 6 1
A deactivateExtension 0 5 1
A removeExtension 0 5 1
A mapVariantToRecommendation 0 11 2
A cancelLicense 0 3 1
A getLinkToApp 0 13 3
A getPriceFromVariant 0 9 2
1
import type { RawLocation, Location } from 'vue-router';
2
import type { AppModulesService, AppModuleDefinition } from 'src/core/service/api/app-modules.service';
3
import type StoreApiService from 'src/core/service/api/store.api.service';
4
import type { ShopwareDiscountCampaignService } from 'src/app/service/discount-campaign.service';
5
import type {
6
    ExtensionStoreActionService,
7
    Extension,
8
    ExtensionVariant,
9
    ExtensionVariantType,
10
    ExtensionType,
11
} from './extension-store-action.service';
12
13
type EXTENSION_VARIANT_TYPES = {
14
    [Property in Uppercase<ExtensionVariantType>]: Lowercase<Property>
15
}
16
17
type EXTENSION_TYPES = {
18
    [Property in Uppercase<ExtensionType>]: Lowercase<Property>
19
}
20
21
interface LabeledLocation extends Location {
22
    label: string|null
23
}
24
25
/**
26
 * @package merchant-services
27
 * @private
28
 */
29
export default class ShopwareExtensionService {
30
    public readonly EXTENSION_VARIANT_TYPES: Readonly<EXTENSION_VARIANT_TYPES>;
31
32
    private readonly EXTENSION_TYPES: Readonly<EXTENSION_TYPES>;
33
34
    constructor(
35
        private readonly appModulesService: AppModulesService,
36
        private readonly extensionStoreActionService: ExtensionStoreActionService,
37
        private readonly discountCampaignService: ShopwareDiscountCampaignService,
38
        private readonly storeApiService: StoreApiService,
39
    ) {
40
        this.EXTENSION_VARIANT_TYPES = Object.freeze({
41
            RENT: 'rent',
42
            BUY: 'buy',
43
            FREE: 'free',
44
        });
45
46
        this.EXTENSION_TYPES = Object.freeze({
47
            APP: 'app',
48
            PLUGIN: 'plugin',
49
        });
50
    }
51
52
    public async installExtension(extensionName: string, type: ExtensionType): Promise<void> {
53
        await this.extensionStoreActionService.installExtension(extensionName, type);
54
55
        await this.updateExtensionData();
56
    }
57
58
    public async updateExtension(extensionName: string, type: ExtensionType, allowNewPrivileges = false): Promise<void> {
59
        await this.extensionStoreActionService.updateExtension(extensionName, type, allowNewPrivileges);
60
61
        await this.updateExtensionData();
62
    }
63
64
    public async uninstallExtension(extensionName: string, type: ExtensionType, removeData: boolean): Promise<void> {
65
        await this.extensionStoreActionService.uninstallExtension(extensionName, type, removeData);
66
67
        await this.updateExtensionData();
68
    }
69
70
    public async removeExtension(extensionName: string, type: ExtensionType): Promise<void> {
71
        await this.extensionStoreActionService.removeExtension(extensionName, type);
72
73
        await this.updateExtensionData();
74
    }
75
76
    public async cancelLicense(licenseId: number): Promise<void> {
77
        await this.extensionStoreActionService.cancelLicense(licenseId);
78
    }
79
80
    public async activateExtension(extensionId: string, type: ExtensionType): Promise<void> {
81
        await this.extensionStoreActionService.activateExtension(extensionId, type);
82
83
        await this.updateModules();
84
    }
85
86
    public async deactivateExtension(extensionId: string, type: ExtensionType): Promise<void> {
87
        await this.extensionStoreActionService.deactivateExtension(extensionId, type);
88
89
        await this.updateModules();
90
    }
91
92
    public async updateExtensionData(): Promise<void> {
93
        Shopware.State.commit('shopwareExtensions/loadMyExtensions');
94
95
        try {
96
            await this.extensionStoreActionService.refresh();
97
98
            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
99
            const myExtensions = await this.extensionStoreActionService.getMyExtensions();
100
101
            Shopware.State.commit('shopwareExtensions/myExtensions', myExtensions);
102
103
            await this.updateModules();
104
        } finally {
105
            Shopware.State.commit('shopwareExtensions/setLoading', false);
106
        }
107
    }
108
109
    public async checkLogin(): Promise<void> {
110
        try {
111
            const { userInfo } = await this.storeApiService.checkLogin();
112
            Shopware.State.commit('shopwareExtensions/setUserInfo', userInfo);
113
        } catch {
114
            Shopware.State.commit('shopwareExtensions/setUserInfo', null);
115
        }
116
    }
117
118
    public orderVariantsByRecommendation(variants: ExtensionVariant[]): ExtensionVariant[] {
119
        const discounted = variants.filter((variant) => this.isVariantDiscounted(variant));
120
        const notDiscounted = variants.filter((variant) => !this.isVariantDiscounted(variant));
121
122
        return [
123
            ...this.orderByType(discounted),
124
            ...this.orderByType(notDiscounted),
125
        ];
126
    }
127
128
    public isVariantDiscounted(variant: ExtensionVariant): boolean {
129
        if (!variant || !variant.discountCampaign
130
            || typeof variant.discountCampaign.discountedPrice !== 'number'
131
            || variant.discountCampaign.discountedPrice === variant.netPrice
132
        ) {
133
            return false;
134
        }
135
136
        return this.discountCampaignService.isDiscountCampaignActive(variant.discountCampaign);
137
    }
138
139
    public getPriceFromVariant(variant: ExtensionVariant) {
140
        if (this.isVariantDiscounted(variant)) {
141
            // null assertion is fine here because we do all checks in isVariantDiscounted
142
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
143
            return variant.discountCampaign!.discountedPrice!;
144
        }
145
146
        return variant.netPrice;
147
    }
148
149
    public mapVariantToRecommendation(variant: ExtensionVariant) {
150
        switch (variant.type) {
151
            case this.EXTENSION_VARIANT_TYPES.FREE:
152
                return 0;
153
            case this.EXTENSION_VARIANT_TYPES.RENT:
154
                return 1;
155
            case this.EXTENSION_VARIANT_TYPES.BUY:
156
                return 2;
157
            default:
158
                return 3;
159
        }
160
    }
161
162
    public async getOpenLink(extension: Extension): Promise<null|RawLocation> {
163
        if (extension.isTheme) {
164
            return this.getLinkToTheme(extension);
165
        }
166
167
        if (extension.type === this.EXTENSION_TYPES.APP) {
168
            return this.getLinkToApp(extension);
169
        }
170
171
        // Only show open link when extension is active. The route is maybe not available
172
        if (!extension.active) {
173
            return null;
174
        }
175
176
        /* eslint-disable @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment */
177
        const entryRoutes = Shopware.State.get('extensionEntryRoutes').routes;
178
179
        if (entryRoutes[extension.name] !== undefined) {
180
            return {
181
                name: entryRoutes[extension.name].route,
182
                label: entryRoutes[extension.name].label ?? null,
183
            } as LabeledLocation;
184
        }
185
        /* eslint-enable */
186
187
        return null;
188
    }
189
190
    private async updateModules() {
191
        const modules = await this.appModulesService.fetchAppModules();
192
193
        Shopware.State.commit('shopwareApps/setApps', modules);
194
    }
195
196
    private async getLinkToTheme(extension: Extension) {
197
        const { Criteria } = Shopware.Data;
198
        const themeRepository = Shopware.Service('repositoryFactory').create('theme');
199
200
        const criteria = new Criteria(1, 1);
201
        criteria.addFilter(Criteria.equals('technicalName', extension.name));
202
203
        const { data: ids } = await themeRepository.searchIds(criteria);
204
205
        if (ids.length === 0) {
206
            return null;
207
        }
208
209
        return {
210
            name: 'sw.theme.manager.detail',
211
            params: {
212
                id: ids[0],
213
            },
214
        };
215
    }
216
217
    private getLinkToApp(extension: Extension) {
218
        const app = this.getAppFromStore(extension.name);
219
220
        if (!app) {
221
            return null;
222
        }
223
224
        if (this.appHasMainModule(app)) {
225
            return this.createLinkToModule(app.name);
226
        }
227
228
        return null;
229
    }
230
231
    private getAppFromStore(extensionName: string) {
232
        return Shopware.State.get('shopwareApps').apps.find((innerApp) => {
233
            return innerApp.name === extensionName;
234
        });
235
    }
236
237
    private appHasMainModule(app: AppModuleDefinition) {
238
        return !!app.mainModule?.source;
239
    }
240
241
    private createLinkToModule(appName: string) {
242
        return {
243
            name: 'sw.extension.module',
244
            params: {
245
                appName,
246
            },
247
        };
248
    }
249
250
    private orderByType(variants: ExtensionVariant[]) {
251
        const valueTypes = variants.map((variant, index) => {
252
            return { value: this.mapVariantToRecommendation(variant), index };
253
        });
254
255
        valueTypes.sort((first, second) => {
256
            return first.value - second.value;
257
        });
258
259
        return valueTypes.map((type) => {
260
            return variants[type.index];
261
        });
262
    }
263
}
264