aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-01-03 01:02:00 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-02-08 21:41:27 +0100
commit3b7d52abb0e7de00bdf92ee3482a4cae1f6b7d64 (patch)
tree7122ef45a1fd42c7ab835ab2f2685050de2c45b2
parentchore(deps): update electron to version 17.0.0 (diff)
downloadsophie-3b7d52abb0e7de00bdf92ee3482a4cae1f6b7d64.tar.gz
sophie-3b7d52abb0e7de00bdf92ee3482a4cae1f6b7d64.tar.zst
sophie-3b7d52abb0e7de00bdf92ee3482a4cae1f6b7d64.zip
feat: Add Profile and Service stores
In the main process, it is optional to specify the ID of a Profile or a Service. The missing ID will be filled in with a randomly generated one. Moreover, services without a profile will get a profile generated with the same name. Signed-off-by: Kristóf Marussy <kristof@marussy.com>
-rw-r--r--packages/main/package.json5
-rw-r--r--packages/main/src/controllers/initConfig.ts12
-rw-r--r--packages/main/src/stores/Config.ts32
-rw-r--r--packages/main/src/stores/Profile.ts51
-rw-r--r--packages/main/src/stores/Service.ts63
-rw-r--r--packages/main/src/stores/__tests__/Config.spec.ts156
-rw-r--r--packages/main/src/utils/generateId.ts27
-rw-r--r--packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts27
-rw-r--r--packages/preload/src/contextBridge/createSophieRenderer.ts15
-rw-r--r--packages/shared/src/index.ts14
-rw-r--r--packages/shared/src/stores/Config.ts5
-rw-r--r--packages/shared/src/stores/Profile.ts32
-rw-r--r--packages/shared/src/stores/Service.ts37
-rw-r--r--yarn.lock17
14 files changed, 448 insertions, 45 deletions
diff --git a/packages/main/package.json b/packages/main/package.json
index 862b83a..80a93b9 100644
--- a/packages/main/package.json
+++ b/packages/main/package.json
@@ -20,7 +20,9 @@
20 "mobx": "^6.3.13", 20 "mobx": "^6.3.13",
21 "mobx-state-tree": "^5.1.0", 21 "mobx-state-tree": "^5.1.0",
22 "ms": "^2.1.3", 22 "ms": "^2.1.3",
23 "os-name": "^5.0.1" 23 "nanoid": "^3.1.30",
24 "os-name": "^5.0.1",
25 "slug": "^5.2.0"
24 }, 26 },
25 "devDependencies": { 27 "devDependencies": {
26 "@jest/globals": "^27.4.6", 28 "@jest/globals": "^27.4.6",
@@ -28,6 +30,7 @@
28 "@types/lodash-es": "^4.17.5", 30 "@types/lodash-es": "^4.17.5",
29 "@types/ms": "^0.7.31", 31 "@types/ms": "^0.7.31",
30 "@types/node": "^17.0.12", 32 "@types/node": "^17.0.12",
33 "@types/slug": "^5",
31 "electron-devtools-installer": "^3.2.0", 34 "electron-devtools-installer": "^3.2.0",
32 "esbuild": "^0.14.14", 35 "esbuild": "^0.14.14",
33 "git-repo-info": "^2.1.1", 36 "git-repo-info": "^2.1.1",
diff --git a/packages/main/src/controllers/initConfig.ts b/packages/main/src/controllers/initConfig.ts
index 915f451..93be978 100644
--- a/packages/main/src/controllers/initConfig.ts
+++ b/packages/main/src/controllers/initConfig.ts
@@ -19,11 +19,11 @@
19 */ 19 */
20 20
21import { debounce } from 'lodash-es'; 21import { debounce } from 'lodash-es';
22import { applySnapshot, getSnapshot, onSnapshot } from 'mobx-state-tree'; 22import { getSnapshot, onSnapshot } from 'mobx-state-tree';
23import ms from 'ms'; 23import ms from 'ms';
24 24
25import type ConfigPersistenceService from '../services/ConfigPersistenceService'; 25import type ConfigPersistenceService from '../services/ConfigPersistenceService.js';
26import type { Config, ConfigSnapshotOut } from '../stores/Config'; 26import { Config, ConfigFileIn, ConfigSnapshotOut } from '../stores/Config.js';
27import type Disposer from '../utils/Disposer'; 27import type Disposer from '../utils/Disposer';
28import { getLogger } from '../utils/log'; 28import { getLogger } from '../utils/log';
29 29
@@ -44,12 +44,14 @@ export default async function initConfig(
44 const result = await persistenceService.readConfig(); 44 const result = await persistenceService.readConfig();
45 if (result.found) { 45 if (result.found) {
46 try { 46 try {
47 applySnapshot(config, result.data); 47 // This cast is unsound if the config file is invalid,
48 lastSnapshotOnDisk = getSnapshot(config); 48 // but we'll throw an error in the end anyways.
49 config.loadFromConfigFile(result.data as ConfigFileIn);
49 } catch (error) { 50 } catch (error) {
50 log.error('Failed to apply config snapshot', result.data, error); 51 log.error('Failed to apply config snapshot', result.data, error);
51 } 52 }
52 } 53 }
54 lastSnapshotOnDisk = getSnapshot(config);
53 return result.found; 55 return result.found;
54 } 56 }
55 57
diff --git a/packages/main/src/stores/Config.ts b/packages/main/src/stores/Config.ts
index ca90c0c..e7fc360 100644
--- a/packages/main/src/stores/Config.ts
+++ b/packages/main/src/stores/Config.ts
@@ -19,14 +19,40 @@
19 */ 19 */
20 20
21import { config as originalConfig, ThemeSource } from '@sophie/shared'; 21import { config as originalConfig, ThemeSource } from '@sophie/shared';
22import { Instance } from 'mobx-state-tree'; 22import { applySnapshot, Instance, SnapshotIn } from 'mobx-state-tree';
23
24import { addMissingProfileIds, PartialProfileSnapshotIn } from './Profile';
25import {
26 addMissingServiceIdsAndProfiles,
27 PartialServiceSnapshotIn,
28} from './Service';
23 29
24export const config = originalConfig.actions((self) => ({ 30export const config = originalConfig.actions((self) => ({
25 setThemeSource(mode: ThemeSource) { 31 loadFromConfigFile(snapshot: ConfigFileIn): void {
32 const profiles = addMissingProfileIds(snapshot.profiles);
33 const services = addMissingServiceIdsAndProfiles(
34 snapshot.services,
35 profiles,
36 );
37 applySnapshot(self, {
38 ...snapshot,
39 profiles,
40 services,
41 });
42 },
43 setThemeSource(mode: ThemeSource): void {
26 self.themeSource = mode; 44 self.themeSource = mode;
27 }, 45 },
28})); 46}));
29 47
30export interface Config extends Instance<typeof config> {} 48export interface Config extends Instance<typeof config> {}
31 49
32export type { ConfigSnapshotIn, ConfigSnapshotOut } from '@sophie/shared'; 50export interface ConfigSnapshotIn extends SnapshotIn<typeof config> {}
51
52export interface ConfigFileIn
53 extends Omit<ConfigSnapshotIn, 'profiles' | 'services'> {
54 profiles?: PartialProfileSnapshotIn[] | undefined;
55 services?: PartialServiceSnapshotIn[] | undefined;
56}
57
58export type { ConfigSnapshotOut } from '@sophie/shared';
diff --git a/packages/main/src/stores/Profile.ts b/packages/main/src/stores/Profile.ts
new file mode 100644
index 0000000..4705862
--- /dev/null
+++ b/packages/main/src/stores/Profile.ts
@@ -0,0 +1,51 @@
1/*
2 * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import type { ProfileSnapshotIn } from '@sophie/shared';
22
23import generateId from '../utils/generateId';
24
25export interface PartialProfileSnapshotIn
26 extends Omit<ProfileSnapshotIn, 'id'> {
27 id?: string | undefined;
28}
29
30export function addMissingProfileIds(
31 partialProfiles: PartialProfileSnapshotIn[] | undefined,
32): ProfileSnapshotIn[] {
33 return (partialProfiles ?? []).map((profile) => {
34 const { name } = profile;
35 let { id } = profile;
36 if (typeof id === 'undefined') {
37 id = generateId(name);
38 }
39 return {
40 ...profile,
41 id,
42 };
43 });
44}
45
46export type {
47 Profile,
48 ProfileSnapshotOut,
49 ProfileSnapshotIn,
50} from '@sophie/shared';
51export { profile } from '@sophie/shared';
diff --git a/packages/main/src/stores/Service.ts b/packages/main/src/stores/Service.ts
new file mode 100644
index 0000000..9bc6a43
--- /dev/null
+++ b/packages/main/src/stores/Service.ts
@@ -0,0 +1,63 @@
1/*
2 * Copyright (C) 2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import type { ServiceSnapshotIn } from '@sophie/shared';
22
23import generateId from '../utils/generateId';
24
25import type { ProfileSnapshotIn } from './Profile';
26
27export interface PartialServiceSnapshotIn
28 extends Omit<ServiceSnapshotIn, 'id' | 'profile'> {
29 id?: string | undefined;
30 profile?: string | undefined;
31}
32
33export function addMissingServiceIdsAndProfiles(
34 partialServices: PartialServiceSnapshotIn[] | undefined,
35 profiles: ProfileSnapshotIn[],
36): ServiceSnapshotIn[] {
37 return (partialServices ?? []).map((service) => {
38 const { name } = service;
39 let { id, profile } = service;
40 if (typeof id === 'undefined') {
41 id = generateId(name);
42 }
43 if (typeof profile === 'undefined') {
44 profile = generateId(name);
45 profiles.push({
46 id: profile,
47 name: service.name,
48 });
49 }
50 return {
51 ...service,
52 id,
53 profile,
54 };
55 });
56}
57
58export type {
59 Service,
60 ServiceSnapshotOut,
61 ServiceSnapshotIn,
62} from '@sophie/shared';
63export { service } from '@sophie/shared';
diff --git a/packages/main/src/stores/__tests__/Config.spec.ts b/packages/main/src/stores/__tests__/Config.spec.ts
new file mode 100644
index 0000000..22ccbc7
--- /dev/null
+++ b/packages/main/src/stores/__tests__/Config.spec.ts
@@ -0,0 +1,156 @@
1/*
2 * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import { config, Config, ConfigFileIn } from '../Config';
22import type { PartialProfileSnapshotIn } from '../Profile';
23import type { PartialServiceSnapshotIn } from '../Service';
24
25const profileProps: PartialProfileSnapshotIn = {
26 name: 'Test profile',
27};
28
29const serviceProps: PartialServiceSnapshotIn = {
30 name: 'Test service',
31 url: 'https://example.com',
32};
33
34let sut: Config;
35
36beforeEach(() => {
37 sut = config.create();
38});
39
40describe('preprocessConfigFile', () => {
41 it('should load profiles with an ID', () => {
42 sut.loadFromConfigFile({
43 profiles: [
44 {
45 id: 'someId',
46 ...profileProps,
47 },
48 ],
49 });
50 expect(sut.profiles[0].id).toBe('someId');
51 });
52
53 it('should generate an ID for profiles without and ID', () => {
54 sut.loadFromConfigFile({
55 profiles: [profileProps],
56 });
57 expect(sut.profiles[0].id).toBeDefined();
58 });
59
60 it('should load services with an ID and a profile', () => {
61 sut.loadFromConfigFile({
62 profiles: [
63 {
64 id: 'someProfileId',
65 ...profileProps,
66 },
67 ],
68 services: [
69 {
70 id: 'someServiceId',
71 profile: 'someProfileId',
72 ...serviceProps,
73 },
74 ],
75 });
76 expect(sut.services[0].id).toBe('someServiceId');
77 expect(sut.services[0].profile).toBe(sut.profiles[0]);
78 });
79
80 it('should refuse to load a profile without a name', () => {
81 expect(() => {
82 sut.loadFromConfigFile({
83 profiles: [
84 {
85 id: 'someProfileId',
86 ...profileProps,
87 name: undefined,
88 },
89 ],
90 } as unknown as ConfigFileIn);
91 }).toThrow();
92 expect(sut.profiles).toHaveLength(0);
93 });
94
95 it('should load services without an ID but with a profile', () => {
96 sut.loadFromConfigFile({
97 profiles: [
98 {
99 id: 'someProfileId',
100 ...profileProps,
101 },
102 ],
103 services: [
104 {
105 profile: 'someProfileId',
106 ...serviceProps,
107 },
108 ],
109 });
110 expect(sut.services[0].id).toBeDefined();
111 expect(sut.services[0].profile).toBe(sut.profiles[0]);
112 });
113
114 it('should create a profile for a service with an ID but no profile', () => {
115 sut.loadFromConfigFile({
116 services: [
117 {
118 id: 'someServiceId',
119 ...serviceProps,
120 },
121 ],
122 });
123 expect(sut.services[0].id).toBe('someServiceId');
124 expect(sut.services[0].profile).toBeDefined();
125 expect(sut.services[0].profile.name).toBe(serviceProps.name);
126 });
127
128 it('should create a profile for a service without an ID or profile', () => {
129 sut.loadFromConfigFile({
130 services: [
131 {
132 ...serviceProps,
133 },
134 ],
135 });
136 expect(sut.services[0].id).toBeDefined();
137 expect(sut.services[0].profile).toBeDefined();
138 expect(sut.services[0].profile.name).toBe(serviceProps.name);
139 });
140
141 it('should refuse to load a service without a name', () => {
142 expect(() => {
143 sut.loadFromConfigFile({
144 services: [
145 {
146 id: 'someServiceId',
147 ...serviceProps,
148 name: undefined,
149 },
150 ],
151 } as unknown as ConfigFileIn);
152 }).toThrow();
153 expect(sut.profiles).toHaveLength(0);
154 expect(sut.services).toHaveLength(0);
155 });
156});
diff --git a/packages/main/src/utils/generateId.ts b/packages/main/src/utils/generateId.ts
new file mode 100644
index 0000000..8a87e5a
--- /dev/null
+++ b/packages/main/src/utils/generateId.ts
@@ -0,0 +1,27 @@
1/*
2 * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import { nanoid } from 'nanoid';
22import slug from 'slug';
23
24export default function generateId(name?: string | undefined) {
25 const nameSlug = typeof name === 'undefined' ? '' : slug(name);
26 return `${nameSlug}_${nanoid()}`;
27}
diff --git a/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts b/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts
index 88b0077..2652c4e 100644
--- a/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts
+++ b/packages/preload/src/contextBridge/__tests__/createSophieRenderer.spec.ts
@@ -51,10 +51,6 @@ const snapshot: SharedStoreSnapshotIn = {
51 shouldUseDarkColors: true, 51 shouldUseDarkColors: true,
52}; 52};
53 53
54const invalidSnapshot = {
55 shouldUseDarkColors: -1,
56} as unknown as SharedStoreSnapshotIn;
57
58const patch: IJsonPatch = { 54const patch: IJsonPatch = {
59 op: 'replace', 55 op: 'replace',
60 path: 'foo', 56 path: 'foo',
@@ -121,14 +117,6 @@ describe('SharedStoreConnector', () => {
121 ).rejects.not.toHaveProperty('message', expect.stringMatching(/s3cr3t/)); 117 ).rejects.not.toHaveProperty('message', expect.stringMatching(/s3cr3t/));
122 expect(listener.onSnapshot).not.toHaveBeenCalled(); 118 expect(listener.onSnapshot).not.toHaveBeenCalled();
123 }); 119 });
124
125 it('should not pass on invalid snapshots', async () => {
126 mocked(ipcRenderer.invoke).mockResolvedValueOnce(invalidSnapshot);
127 await expect(sut.onSharedStoreChange(listener)).rejects.toBeInstanceOf(
128 Error,
129 );
130 expect(listener.onSnapshot).not.toHaveBeenCalled();
131 });
132 }); 120 });
133 121
134 describe('dispatchAction', () => { 122 describe('dispatchAction', () => {
@@ -220,21 +208,6 @@ describe('SharedStoreConnector', () => {
220 itDoesNotPassPatchesToTheListener(); 208 itDoesNotPassPatchesToTheListener();
221 }); 209 });
222 210
223 describe('when a listener failed to register due to an invalid snapshot', () => {
224 beforeEach(async () => {
225 mocked(ipcRenderer.invoke).mockResolvedValueOnce(invalidSnapshot);
226 try {
227 await sut.onSharedStoreChange(listener);
228 } catch {
229 // Ignore error.
230 }
231 });
232
233 itRefusesToRegisterAnotherListener();
234
235 itDoesNotPassPatchesToTheListener();
236 });
237
238 describe('when a listener failed to register due to listener error', () => { 211 describe('when a listener failed to register due to listener error', () => {
239 beforeEach(async () => { 212 beforeEach(async () => {
240 mocked(ipcRenderer.invoke).mockResolvedValueOnce(snapshot); 213 mocked(ipcRenderer.invoke).mockResolvedValueOnce(snapshot);
diff --git a/packages/preload/src/contextBridge/createSophieRenderer.ts b/packages/preload/src/contextBridge/createSophieRenderer.ts
index 3174fed..41accfd 100644
--- a/packages/preload/src/contextBridge/createSophieRenderer.ts
+++ b/packages/preload/src/contextBridge/createSophieRenderer.ts
@@ -23,8 +23,8 @@ import {
23 action, 23 action,
24 MainToRendererIpcMessage, 24 MainToRendererIpcMessage,
25 RendererToMainIpcMessage, 25 RendererToMainIpcMessage,
26 sharedStore,
27 SharedStoreListener, 26 SharedStoreListener,
27 SharedStoreSnapshotIn,
28 SophieRenderer, 28 SophieRenderer,
29} from '@sophie/shared'; 29} from '@sophie/shared';
30import { ipcRenderer } from 'electron'; 30import { ipcRenderer } from 'electron';
@@ -66,15 +66,12 @@ class SharedStoreConnector {
66 } catch (error) { 66 } catch (error) {
67 log.error('Failed to get initial shared store snapshot', error); 67 log.error('Failed to get initial shared store snapshot', error);
68 } 68 }
69 if (success) { 69 if (!success) {
70 if (sharedStore.is(snapshot)) { 70 throw new Error('Failed to connect to shared store');
71 listener.onSnapshot(snapshot);
72 this.listener = listener;
73 return;
74 }
75 log.error('Got invalid initial shared store snapshot', snapshot);
76 } 71 }
77 throw new Error('Failed to connect to shared store'); 72 // `mobx-state-tree` will validate the snapshot, so we can safely cast here.
73 listener.onSnapshot(snapshot as SharedStoreSnapshotIn);
74 this.listener = listener;
78 } 75 }
79} 76}
80 77
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 6383f63..9f4e9b3 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -33,6 +33,20 @@ export type {
33export { config } from './stores/Config'; 33export { config } from './stores/Config';
34 34
35export type { 35export type {
36 Profile,
37 ProfileSnapshotIn,
38 ProfileSnapshotOut,
39} from './stores/Profile';
40export { profile } from './stores/Profile';
41
42export type {
43 Service,
44 ServiceSnapshotIn,
45 ServiceSnapshotOut,
46} from './stores/Service';
47export { service } from './stores/Service';
48
49export type {
36 SharedStore, 50 SharedStore,
37 SharedStoreListener, 51 SharedStoreListener,
38 SharedStoreSnapshotIn, 52 SharedStoreSnapshotIn,
diff --git a/packages/shared/src/stores/Config.ts b/packages/shared/src/stores/Config.ts
index 1d98a33..1d97e7d 100644
--- a/packages/shared/src/stores/Config.ts
+++ b/packages/shared/src/stores/Config.ts
@@ -22,7 +22,12 @@ import { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree';
22 22
23import { themeSource } from '../schemas'; 23import { themeSource } from '../schemas';
24 24
25import { profile } from './Profile';
26import { service } from './Service';
27
25export const config = types.model('Config', { 28export const config = types.model('Config', {
29 profiles: types.array(profile),
30 services: types.array(service),
26 themeSource: types.optional(types.enumeration(themeSource.options), 'system'), 31 themeSource: types.optional(types.enumeration(themeSource.options), 'system'),
27}); 32});
28 33
diff --git a/packages/shared/src/stores/Profile.ts b/packages/shared/src/stores/Profile.ts
new file mode 100644
index 0000000..88a0f4d
--- /dev/null
+++ b/packages/shared/src/stores/Profile.ts
@@ -0,0 +1,32 @@
1/*
2 * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree';
22
23export const profile = types.model('Profile', {
24 id: types.identifier,
25 name: types.string,
26});
27
28export interface Profile extends Instance<typeof profile> {}
29
30export interface ProfileSnapshotIn extends SnapshotIn<typeof profile> {}
31
32export interface ProfileSnapshotOut extends SnapshotOut<typeof profile> {}
diff --git a/packages/shared/src/stores/Service.ts b/packages/shared/src/stores/Service.ts
new file mode 100644
index 0000000..ed2cd9a
--- /dev/null
+++ b/packages/shared/src/stores/Service.ts
@@ -0,0 +1,37 @@
1/*
2 * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import { Instance, types, SnapshotIn, SnapshotOut } from 'mobx-state-tree';
22
23import { profile } from './Profile';
24
25export const service = types.model('Service', {
26 id: types.identifier,
27 name: types.string,
28 profile: types.reference(profile),
29 // TODO: Remove this once recipes are added.
30 url: types.string,
31});
32
33export interface Service extends Instance<typeof service> {}
34
35export interface ServiceSnapshotIn extends SnapshotIn<typeof service> {}
36
37export interface ServiceSnapshotOut extends SnapshotOut<typeof service> {}
diff --git a/yarn.lock b/yarn.lock
index c131687..eba34a3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1241,6 +1241,7 @@ __metadata:
1241 "@types/lodash-es": ^4.17.5 1241 "@types/lodash-es": ^4.17.5
1242 "@types/ms": ^0.7.31 1242 "@types/ms": ^0.7.31
1243 "@types/node": ^17.0.12 1243 "@types/node": ^17.0.12
1244 "@types/slug": ^5
1244 chalk: ^5.0.0 1245 chalk: ^5.0.0
1245 electron: 17.0.0 1246 electron: 17.0.0
1246 electron-devtools-installer: ^3.2.0 1247 electron-devtools-installer: ^3.2.0
@@ -1256,7 +1257,9 @@ __metadata:
1256 mobx: ^6.3.13 1257 mobx: ^6.3.13
1257 mobx-state-tree: ^5.1.0 1258 mobx-state-tree: ^5.1.0
1258 ms: ^2.1.3 1259 ms: ^2.1.3
1260 nanoid: ^3.1.30
1259 os-name: ^5.0.1 1261 os-name: ^5.0.1
1262 slug: ^5.2.0
1260 languageName: unknown 1263 languageName: unknown
1261 linkType: soft 1264 linkType: soft
1262 1265
@@ -1653,6 +1656,13 @@ __metadata:
1653 languageName: node 1656 languageName: node
1654 linkType: hard 1657 linkType: hard
1655 1658
1659"@types/slug@npm:^5":
1660 version: 5.0.3
1661 resolution: "@types/slug@npm:5.0.3"
1662 checksum: 17b90c7ebc57f7aeeb8d49ef17445f1edf7f704440c23e4d326169faeef0e76191548ae2bcb6ba0667da710c5e2ad7e8b96fe52d5adc531b891b37327564a967
1663 languageName: node
1664 linkType: hard
1665
1656"@types/stack-utils@npm:^2.0.0": 1666"@types/stack-utils@npm:^2.0.0":
1657 version: 2.0.1 1667 version: 2.0.1
1658 resolution: "@types/stack-utils@npm:2.0.1" 1668 resolution: "@types/stack-utils@npm:2.0.1"
@@ -8141,6 +8151,13 @@ __metadata:
8141 languageName: node 8151 languageName: node
8142 linkType: hard 8152 linkType: hard
8143 8153
8154"slug@npm:^5.2.0":
8155 version: 5.2.0
8156 resolution: "slug@npm:5.2.0"
8157 checksum: ffb75ad26199445b564c9e076df09e887fdc047c945eb050ac6bf7f4f45234fefcb030bb0fa2668884df07f809db78e5226e647a956afbd5eb6aca877ca8e276
8158 languageName: node
8159 linkType: hard
8160
8144"smart-buffer@npm:^4.0.2, smart-buffer@npm:^4.1.0": 8161"smart-buffer@npm:^4.0.2, smart-buffer@npm:^4.1.0":
8145 version: 4.2.0 8162 version: 4.2.0
8146 resolution: "smart-buffer@npm:4.2.0" 8163 resolution: "smart-buffer@npm:4.2.0"