@htlkg/data 0.0.14 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +72 -0
  2. package/dist/client/index.d.ts +123 -30
  3. package/dist/client/index.js +75 -1
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/hooks/index.d.ts +76 -2
  6. package/dist/hooks/index.js +224 -6
  7. package/dist/hooks/index.js.map +1 -1
  8. package/dist/index.d.ts +6 -4
  9. package/dist/index.js +550 -7
  10. package/dist/index.js.map +1 -1
  11. package/dist/mutations/index.d.ts +149 -5
  12. package/dist/mutations/index.js +397 -0
  13. package/dist/mutations/index.js.map +1 -1
  14. package/dist/productInstances-CzT3NZKU.d.ts +98 -0
  15. package/dist/queries/index.d.ts +54 -2
  16. package/dist/queries/index.js +60 -1
  17. package/dist/queries/index.js.map +1 -1
  18. package/dist/server/index.d.ts +47 -0
  19. package/dist/server/index.js +59 -0
  20. package/dist/server/index.js.map +1 -0
  21. package/package.json +5 -1
  22. package/src/client/index.ts +82 -3
  23. package/src/client/proxy.ts +170 -0
  24. package/src/hooks/index.ts +1 -0
  25. package/src/hooks/useProductInstances.ts +174 -0
  26. package/src/index.ts +11 -0
  27. package/src/mutations/accounts.ts +102 -1
  28. package/src/mutations/brands.ts +102 -1
  29. package/src/mutations/index.ts +23 -0
  30. package/src/mutations/productInstances/index.ts +14 -0
  31. package/src/mutations/productInstances/productInstances.integration.test.ts +621 -0
  32. package/src/mutations/productInstances/productInstances.test.ts +680 -0
  33. package/src/mutations/productInstances/productInstances.ts +280 -0
  34. package/src/mutations/systemSettings.ts +130 -0
  35. package/src/mutations/users.ts +102 -1
  36. package/src/queries/index.ts +9 -0
  37. package/src/queries/systemSettings.ts +115 -0
@@ -0,0 +1,280 @@
1
+ /**
2
+ * ProductInstance Mutation Functions
3
+ *
4
+ * Provides mutation functions for creating, updating, and deleting product instances.
5
+ * Product instances represent enabled products for a specific brand with their configuration.
6
+ */
7
+
8
+ import type { ProductInstance } from "@htlkg/core/types";
9
+ import { getClientUser } from "@htlkg/core/auth";
10
+ import { getCurrentTimestamp } from "@htlkg/core/utils";
11
+ import { AppError } from "@htlkg/core/errors";
12
+
13
+ /**
14
+ * Get current user identifier for audit trails
15
+ * Uses getClientUser() and returns email or username, falling back to "system"
16
+ */
17
+ async function getUserIdentifier(fallback = "system"): Promise<string> {
18
+ try {
19
+ const user = await getClientUser();
20
+ if (user) {
21
+ return user.email || user.username || fallback;
22
+ }
23
+ return fallback;
24
+ } catch {
25
+ return fallback;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Input type for creating a product instance
31
+ */
32
+ export interface CreateProductInstanceInput {
33
+ productId: string;
34
+ brandId: string;
35
+ accountId: string;
36
+ productName: string;
37
+ enabled: boolean;
38
+ config?: Record<string, any>;
39
+ version?: string;
40
+ lastUpdated?: string;
41
+ updatedBy?: string;
42
+ }
43
+
44
+ /**
45
+ * Input type for updating a product instance
46
+ */
47
+ export interface UpdateProductInstanceInput {
48
+ id: string;
49
+ enabled?: boolean;
50
+ config?: Record<string, any>;
51
+ version?: string;
52
+ lastUpdated?: string;
53
+ updatedBy?: string;
54
+ }
55
+
56
+ /**
57
+ * Create a new product instance
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * import { createProductInstance } from '@htlkg/data/mutations';
62
+ * import { generateClient } from '@htlkg/data/client';
63
+ *
64
+ * const client = generateClient<Schema>();
65
+ * const instance = await createProductInstance(client, {
66
+ * productId: 'product-123',
67
+ * brandId: 'brand-456',
68
+ * accountId: 'account-789',
69
+ * enabled: true,
70
+ * config: { apiKey: 'xxx', maxRequests: 100 }
71
+ * });
72
+ * ```
73
+ */
74
+ export async function createProductInstance<TClient = any>(
75
+ client: TClient,
76
+ input: CreateProductInstanceInput,
77
+ ): Promise<ProductInstance | null> {
78
+ try {
79
+ // Build input - manually construct to avoid Vue Proxy issues
80
+ const createInput: any = {
81
+ productId: input.productId,
82
+ productName: input.productName,
83
+ brandId: input.brandId,
84
+ accountId: input.accountId,
85
+ enabled: input.enabled,
86
+ version: input.version,
87
+ lastUpdated: input.lastUpdated || getCurrentTimestamp(),
88
+ updatedBy: input.updatedBy || await getUserIdentifier(),
89
+ };
90
+
91
+ // AWSJSON type requires JSON STRING
92
+ if (input.config) {
93
+ // Double stringify: first to strip Vue Proxy, second to create JSON string
94
+ createInput.config = JSON.stringify(JSON.parse(JSON.stringify(input.config)));
95
+ }
96
+
97
+ console.log("[createProductInstance] Config as string:", createInput.config);
98
+ console.log("[createProductInstance] Config type:", typeof createInput.config);
99
+
100
+ const { data, errors } = await (client as any).models.ProductInstance.create(createInput);
101
+
102
+ if (errors) {
103
+ console.error("[createProductInstance] GraphQL errors:", errors);
104
+ throw new AppError(
105
+ "Failed to create product instance",
106
+ "PRODUCT_INSTANCE_CREATE_ERROR",
107
+ 500,
108
+ { errors }
109
+ );
110
+ }
111
+
112
+ return data as ProductInstance;
113
+ } catch (error) {
114
+ console.error("[createProductInstance] Error creating product instance:", error);
115
+ if (error instanceof AppError) {
116
+ throw error;
117
+ }
118
+ throw new AppError(
119
+ "Failed to create product instance",
120
+ "PRODUCT_INSTANCE_CREATE_ERROR",
121
+ 500,
122
+ { originalError: error }
123
+ );
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Update an existing product instance
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * import { updateProductInstance } from '@htlkg/data/mutations';
133
+ * import { generateClient } from '@htlkg/data/client';
134
+ *
135
+ * const client = generateClient<Schema>();
136
+ * const instance = await updateProductInstance(client, {
137
+ * id: 'instance-123',
138
+ * enabled: false,
139
+ * config: { apiKey: 'new-key', maxRequests: 200 }
140
+ * });
141
+ * ```
142
+ */
143
+ export async function updateProductInstance<TClient = any>(
144
+ client: TClient,
145
+ input: UpdateProductInstanceInput,
146
+ ): Promise<ProductInstance | null> {
147
+ try {
148
+ // Add timestamp and user metadata if not provided
149
+ // Convert config from Vue Proxy to plain object
150
+ const updateInput: any = {
151
+ ...input,
152
+ lastUpdated: input.lastUpdated || getCurrentTimestamp(),
153
+ updatedBy: input.updatedBy || await getUserIdentifier(),
154
+ };
155
+
156
+ // AWSJSON type requires JSON STRING
157
+ if (input.config) {
158
+ updateInput.config = JSON.stringify(JSON.parse(JSON.stringify(input.config)));
159
+ }
160
+
161
+ const { data, errors } = await (client as any).models.ProductInstance.update(updateInput);
162
+
163
+ if (errors) {
164
+ console.error("[updateProductInstance] GraphQL errors:", errors);
165
+ throw new AppError(
166
+ "Failed to update product instance",
167
+ "PRODUCT_INSTANCE_UPDATE_ERROR",
168
+ 500,
169
+ { errors }
170
+ );
171
+ }
172
+
173
+ return data as ProductInstance;
174
+ } catch (error) {
175
+ console.error("[updateProductInstance] Error updating product instance:", error);
176
+ if (error instanceof AppError) {
177
+ throw error;
178
+ }
179
+ throw new AppError(
180
+ "Failed to update product instance",
181
+ "PRODUCT_INSTANCE_UPDATE_ERROR",
182
+ 500,
183
+ { originalError: error }
184
+ );
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Delete a product instance
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * import { deleteProductInstance } from '@htlkg/data/mutations';
194
+ * import { generateClient } from '@htlkg/data/client';
195
+ *
196
+ * const client = generateClient<Schema>();
197
+ * await deleteProductInstance(client, 'instance-123');
198
+ * ```
199
+ */
200
+ export async function deleteProductInstance<TClient = any>(
201
+ client: TClient,
202
+ id: string,
203
+ ): Promise<boolean> {
204
+ try {
205
+ const { errors } = await (client as any).models.ProductInstance.delete({ id });
206
+
207
+ if (errors) {
208
+ console.error("[deleteProductInstance] GraphQL errors:", errors);
209
+ throw new AppError(
210
+ "Failed to delete product instance",
211
+ "PRODUCT_INSTANCE_DELETE_ERROR",
212
+ 500,
213
+ { errors }
214
+ );
215
+ }
216
+
217
+ return true;
218
+ } catch (error) {
219
+ console.error("[deleteProductInstance] Error deleting product instance:", error);
220
+ if (error instanceof AppError) {
221
+ throw error;
222
+ }
223
+ throw new AppError(
224
+ "Failed to delete product instance",
225
+ "PRODUCT_INSTANCE_DELETE_ERROR",
226
+ 500,
227
+ { originalError: error }
228
+ );
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Toggle the enabled status of a product instance
234
+ *
235
+ * @example
236
+ * ```typescript
237
+ * import { toggleProductInstanceEnabled } from '@htlkg/data/mutations';
238
+ * import { generateClient } from '@htlkg/data/client';
239
+ *
240
+ * const client = generateClient<Schema>();
241
+ * const instance = await toggleProductInstanceEnabled(client, 'instance-123', true);
242
+ * ```
243
+ */
244
+ export async function toggleProductInstanceEnabled<TClient = any>(
245
+ client: TClient,
246
+ id: string,
247
+ enabled: boolean,
248
+ ): Promise<ProductInstance | null> {
249
+ try {
250
+ const { data, errors } = await (client as any).models.ProductInstance.update({
251
+ id,
252
+ enabled,
253
+ lastUpdated: getCurrentTimestamp(),
254
+ updatedBy: await getUserIdentifier(),
255
+ });
256
+
257
+ if (errors) {
258
+ console.error("[toggleProductInstanceEnabled] GraphQL errors:", errors);
259
+ throw new AppError(
260
+ "Failed to toggle product instance",
261
+ "PRODUCT_INSTANCE_TOGGLE_ERROR",
262
+ 500,
263
+ { errors }
264
+ );
265
+ }
266
+
267
+ return data as ProductInstance;
268
+ } catch (error) {
269
+ console.error("[toggleProductInstanceEnabled] Error toggling product instance:", error);
270
+ if (error instanceof AppError) {
271
+ throw error;
272
+ }
273
+ throw new AppError(
274
+ "Failed to toggle product instance",
275
+ "PRODUCT_INSTANCE_TOGGLE_ERROR",
276
+ 500,
277
+ { originalError: error }
278
+ );
279
+ }
280
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * SystemSettings Mutation Functions
3
+ *
4
+ * Provides mutation functions for managing system settings.
5
+ */
6
+
7
+ import type { SystemSettings } from "@htlkg/core/types";
8
+ import {
9
+ SYSTEM_SETTINGS_KEY,
10
+ DEFAULT_SOFT_DELETE_RETENTION_DAYS,
11
+ } from "../queries/systemSettings";
12
+
13
+ /**
14
+ * Input type for updating system settings
15
+ */
16
+ export interface UpdateSystemSettingsInput {
17
+ softDeleteRetentionDays: number;
18
+ updatedBy: string;
19
+ }
20
+
21
+ /**
22
+ * Update or create system settings
23
+ * Only SUPER_ADMINS can update system settings
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * import { updateSystemSettings } from '@htlkg/data/mutations';
28
+ * import { generateClient } from '@htlkg/data/client';
29
+ *
30
+ * const client = generateClient<Schema>();
31
+ * const settings = await updateSystemSettings(client, {
32
+ * softDeleteRetentionDays: 60,
33
+ * updatedBy: 'admin@example.com'
34
+ * });
35
+ * ```
36
+ */
37
+ export async function updateSystemSettings<TClient = any>(
38
+ client: TClient,
39
+ input: UpdateSystemSettingsInput,
40
+ ): Promise<SystemSettings | null> {
41
+ try {
42
+ // Validate retention days (minimum 1 day, maximum 365 days)
43
+ if (input.softDeleteRetentionDays < 1 || input.softDeleteRetentionDays > 365) {
44
+ console.error(
45
+ "[updateSystemSettings] Invalid retention days. Must be between 1 and 365.",
46
+ );
47
+ return null;
48
+ }
49
+
50
+ // First, try to get existing settings
51
+ const { data: existing } = await (client as any).models.SystemSettings.get({
52
+ key: SYSTEM_SETTINGS_KEY,
53
+ });
54
+
55
+ const settingsData = {
56
+ key: SYSTEM_SETTINGS_KEY,
57
+ softDeleteRetentionDays: input.softDeleteRetentionDays,
58
+ updatedAt: new Date().toISOString(),
59
+ updatedBy: input.updatedBy,
60
+ };
61
+
62
+ let result;
63
+ if (existing) {
64
+ // Update existing settings
65
+ result = await (client as any).models.SystemSettings.update(settingsData);
66
+ } else {
67
+ // Create new settings
68
+ result = await (client as any).models.SystemSettings.create(settingsData);
69
+ }
70
+
71
+ if (result.errors) {
72
+ console.error("[updateSystemSettings] GraphQL errors:", result.errors);
73
+ return null;
74
+ }
75
+
76
+ return result.data as SystemSettings;
77
+ } catch (error) {
78
+ console.error("[updateSystemSettings] Error updating system settings:", error);
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Initialize system settings with default values if they don't exist
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * import { initializeSystemSettings } from '@htlkg/data/mutations';
89
+ * import { generateClient } from '@htlkg/data/client';
90
+ *
91
+ * const client = generateClient<Schema>();
92
+ * const settings = await initializeSystemSettings(client, 'system@hotelinking.com');
93
+ * ```
94
+ */
95
+ export async function initializeSystemSettings<TClient = any>(
96
+ client: TClient,
97
+ initializedBy: string,
98
+ ): Promise<SystemSettings | null> {
99
+ try {
100
+ // Check if settings already exist
101
+ const { data: existing } = await (client as any).models.SystemSettings.get({
102
+ key: SYSTEM_SETTINGS_KEY,
103
+ });
104
+
105
+ if (existing) {
106
+ return existing as SystemSettings;
107
+ }
108
+
109
+ // Create default settings
110
+ const { data, errors } = await (client as any).models.SystemSettings.create({
111
+ key: SYSTEM_SETTINGS_KEY,
112
+ softDeleteRetentionDays: DEFAULT_SOFT_DELETE_RETENTION_DAYS,
113
+ updatedAt: new Date().toISOString(),
114
+ updatedBy: initializedBy,
115
+ });
116
+
117
+ if (errors) {
118
+ console.error("[initializeSystemSettings] GraphQL errors:", errors);
119
+ return null;
120
+ }
121
+
122
+ return data as SystemSettings;
123
+ } catch (error) {
124
+ console.error(
125
+ "[initializeSystemSettings] Error initializing system settings:",
126
+ error,
127
+ );
128
+ throw error;
129
+ }
130
+ }
@@ -5,6 +5,10 @@
5
5
  */
6
6
 
7
7
  import type { User } from "@htlkg/core/types";
8
+ import {
9
+ checkRestoreEligibility,
10
+ DEFAULT_SOFT_DELETE_RETENTION_DAYS,
11
+ } from "../queries/systemSettings";
8
12
 
9
13
  /**
10
14
  * Input type for creating a user
@@ -105,7 +109,104 @@ export async function updateUser<TClient = any>(
105
109
  }
106
110
 
107
111
  /**
108
- * Delete a user
112
+ * Soft delete a user (sets status to "deleted" instead of removing)
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * import { softDeleteUser } from '@htlkg/data/mutations';
117
+ * import { generateClient } from '@htlkg/data/client';
118
+ *
119
+ * const client = generateClient<Schema>();
120
+ * await softDeleteUser(client, 'user-123', 'admin@example.com');
121
+ * ```
122
+ */
123
+ export async function softDeleteUser<TClient = any>(
124
+ client: TClient,
125
+ id: string,
126
+ deletedBy: string,
127
+ ): Promise<boolean> {
128
+ try {
129
+ const { errors } = await (client as any).models.User.update({
130
+ id,
131
+ status: "deleted",
132
+ deletedAt: new Date().toISOString(),
133
+ deletedBy,
134
+ });
135
+
136
+ if (errors) {
137
+ console.error("[softDeleteUser] GraphQL errors:", errors);
138
+ return false;
139
+ }
140
+
141
+ return true;
142
+ } catch (error) {
143
+ console.error("[softDeleteUser] Error soft-deleting user:", error);
144
+ throw error;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Restore a soft-deleted user (sets status back to "active")
150
+ * Checks retention period before allowing restoration
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * import { restoreUser } from '@htlkg/data/mutations';
155
+ * import { generateClient } from '@htlkg/data/client';
156
+ *
157
+ * const client = generateClient<Schema>();
158
+ * await restoreUser(client, 'user-123');
159
+ * // Or with custom retention days:
160
+ * await restoreUser(client, 'user-123', 60);
161
+ * ```
162
+ */
163
+ export async function restoreUser<TClient = any>(
164
+ client: TClient,
165
+ id: string,
166
+ retentionDays: number = DEFAULT_SOFT_DELETE_RETENTION_DAYS,
167
+ ): Promise<{ success: boolean; error?: string }> {
168
+ try {
169
+ // First, get the user to check its deletedAt timestamp
170
+ const { data: user, errors: getErrors } = await (
171
+ client as any
172
+ ).models.User.get({ id });
173
+
174
+ if (getErrors || !user) {
175
+ console.error("[restoreUser] Error fetching user:", getErrors);
176
+ return { success: false, error: "User not found" };
177
+ }
178
+
179
+ // Check if restoration is allowed based on retention period
180
+ const eligibility = checkRestoreEligibility(user.deletedAt, retentionDays);
181
+
182
+ if (!eligibility.canRestore) {
183
+ const errorMsg = `Cannot restore user. Retention period of ${retentionDays} days has expired. Item was deleted ${eligibility.daysExpired} days ago.`;
184
+ console.error("[restoreUser]", errorMsg);
185
+ return { success: false, error: errorMsg };
186
+ }
187
+
188
+ const { errors } = await (client as any).models.User.update({
189
+ id,
190
+ status: "active",
191
+ deletedAt: null,
192
+ deletedBy: null,
193
+ });
194
+
195
+ if (errors) {
196
+ console.error("[restoreUser] GraphQL errors:", errors);
197
+ return { success: false, error: "Failed to restore user" };
198
+ }
199
+
200
+ return { success: true };
201
+ } catch (error) {
202
+ console.error("[restoreUser] Error restoring user:", error);
203
+ throw error;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Hard delete a user (permanently removes from database)
209
+ * Use with caution - prefer softDeleteUser for recoverable deletion
109
210
  *
110
211
  * @example
111
212
  * ```typescript
@@ -43,3 +43,12 @@ export {
43
43
 
44
44
  // Server-side query helpers
45
45
  export { executeServerQuery, executePublicQuery } from "./server-helpers";
46
+
47
+ // SystemSettings queries
48
+ export {
49
+ getSystemSettings,
50
+ getSoftDeleteRetentionDays,
51
+ checkRestoreEligibility,
52
+ SYSTEM_SETTINGS_KEY,
53
+ DEFAULT_SOFT_DELETE_RETENTION_DAYS,
54
+ } from "./systemSettings";
@@ -0,0 +1,115 @@
1
+ /**
2
+ * SystemSettings Query Functions
3
+ *
4
+ * Provides query functions for fetching system settings from the GraphQL API.
5
+ */
6
+
7
+ import type { SystemSettings } from "@htlkg/core/types";
8
+
9
+ /** Default retention days if no settings exist */
10
+ export const DEFAULT_SOFT_DELETE_RETENTION_DAYS = 30;
11
+
12
+ /** The key used for global system settings */
13
+ export const SYSTEM_SETTINGS_KEY = "GLOBAL";
14
+
15
+ /**
16
+ * Get system settings
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { getSystemSettings } from '@htlkg/data/queries';
21
+ * import { generateClient } from '@htlkg/data/client';
22
+ *
23
+ * const client = generateClient<Schema>();
24
+ * const settings = await getSystemSettings(client);
25
+ * ```
26
+ */
27
+ export async function getSystemSettings<TClient = any>(
28
+ client: TClient,
29
+ ): Promise<SystemSettings | null> {
30
+ try {
31
+ const { data, errors } = await (client as any).models.SystemSettings.get({
32
+ key: SYSTEM_SETTINGS_KEY,
33
+ });
34
+
35
+ if (errors) {
36
+ console.error("[getSystemSettings] GraphQL errors:", errors);
37
+ return null;
38
+ }
39
+
40
+ return data as SystemSettings;
41
+ } catch (error) {
42
+ console.error("[getSystemSettings] Error fetching system settings:", error);
43
+ throw error;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Get the soft delete retention days from system settings
49
+ * Returns the default (30 days) if settings don't exist
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * import { getSoftDeleteRetentionDays } from '@htlkg/data/queries';
54
+ * import { generateClient } from '@htlkg/data/client';
55
+ *
56
+ * const client = generateClient<Schema>();
57
+ * const days = await getSoftDeleteRetentionDays(client);
58
+ * ```
59
+ */
60
+ export async function getSoftDeleteRetentionDays<TClient = any>(
61
+ client: TClient,
62
+ ): Promise<number> {
63
+ const settings = await getSystemSettings(client);
64
+ return settings?.softDeleteRetentionDays ?? DEFAULT_SOFT_DELETE_RETENTION_DAYS;
65
+ }
66
+
67
+ /**
68
+ * Check if a soft-deleted item can still be restored based on retention period
69
+ *
70
+ * @param deletedAt - The ISO date string when the item was deleted
71
+ * @param retentionDays - Number of days items can be restored
72
+ * @returns Object with canRestore flag and daysRemaining/daysExpired
73
+ */
74
+ export function checkRestoreEligibility(
75
+ deletedAt: string | undefined | null,
76
+ retentionDays: number,
77
+ ): {
78
+ canRestore: boolean;
79
+ daysRemaining: number;
80
+ daysExpired: number;
81
+ expiresAt: Date | null;
82
+ } {
83
+ if (!deletedAt) {
84
+ return {
85
+ canRestore: false,
86
+ daysRemaining: 0,
87
+ daysExpired: 0,
88
+ expiresAt: null,
89
+ };
90
+ }
91
+
92
+ const deletedDate = new Date(deletedAt);
93
+ const expiresAt = new Date(deletedDate);
94
+ expiresAt.setDate(expiresAt.getDate() + retentionDays);
95
+
96
+ const now = new Date();
97
+ const msRemaining = expiresAt.getTime() - now.getTime();
98
+ const daysRemaining = Math.ceil(msRemaining / (1000 * 60 * 60 * 24));
99
+
100
+ if (daysRemaining <= 0) {
101
+ return {
102
+ canRestore: false,
103
+ daysRemaining: 0,
104
+ daysExpired: Math.abs(daysRemaining),
105
+ expiresAt,
106
+ };
107
+ }
108
+
109
+ return {
110
+ canRestore: true,
111
+ daysRemaining,
112
+ daysExpired: 0,
113
+ expiresAt,
114
+ };
115
+ }