@htlkg/data 0.0.15 → 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.
- package/README.md +72 -0
- package/dist/client/index.d.ts +123 -30
- package/dist/client/index.js +75 -1
- package/dist/client/index.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +4 -3
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +326 -4
- package/dist/index.js.map +1 -1
- package/dist/mutations/index.d.ts +148 -5
- package/dist/mutations/index.js +245 -0
- package/dist/mutations/index.js.map +1 -1
- package/dist/queries/index.d.ts +54 -2
- package/dist/queries/index.js +60 -1
- package/dist/queries/index.js.map +1 -1
- package/dist/server/index.d.ts +47 -0
- package/dist/server/index.js +59 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +6 -2
- package/src/client/index.ts +82 -3
- package/src/client/proxy.ts +170 -0
- package/src/index.ts +11 -0
- package/src/mutations/accounts.ts +102 -1
- package/src/mutations/brands.ts +102 -1
- package/src/mutations/index.ts +13 -0
- package/src/mutations/systemSettings.ts +130 -0
- package/src/mutations/users.ts +102 -1
- package/src/queries/index.ts +9 -0
- package/src/queries/systemSettings.ts +115 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL Proxy Client
|
|
3
|
+
*
|
|
4
|
+
* Client-side helper for making authenticated GraphQL operations through
|
|
5
|
+
* the server-side proxy. This allows Vue components to perform mutations
|
|
6
|
+
* while keeping auth cookies httpOnly for security.
|
|
7
|
+
*
|
|
8
|
+
* @module @htlkg/data/proxy
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Valid GraphQL operations
|
|
13
|
+
*/
|
|
14
|
+
export type Operation = "create" | "update" | "delete" | "get" | "list";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Response type from GraphQL operations
|
|
18
|
+
*/
|
|
19
|
+
export interface GraphQLResponse<T = any> {
|
|
20
|
+
data: T | null;
|
|
21
|
+
errors?: Array<{ message: string; [key: string]: any }>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Options for proxy requests
|
|
26
|
+
*/
|
|
27
|
+
export interface ProxyOptions {
|
|
28
|
+
/** Custom API endpoint (default: /api/graphql) */
|
|
29
|
+
endpoint?: string;
|
|
30
|
+
/** Additional fetch options */
|
|
31
|
+
fetchOptions?: RequestInit;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Default proxy endpoint
|
|
36
|
+
*/
|
|
37
|
+
const DEFAULT_ENDPOINT = "/api/graphql";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Execute a GraphQL mutation through the server proxy
|
|
41
|
+
*
|
|
42
|
+
* @param model - The model name (e.g., 'User', 'Brand', 'Account')
|
|
43
|
+
* @param operation - The operation type ('create', 'update', 'delete')
|
|
44
|
+
* @param data - The operation data/input
|
|
45
|
+
* @param options - Optional configuration
|
|
46
|
+
* @returns Promise with the operation result
|
|
47
|
+
*
|
|
48
|
+
* @example Create
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const result = await mutate('User', 'create', {
|
|
51
|
+
* email: 'user@example.com',
|
|
52
|
+
* accountId: '123',
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @example Update
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const result = await mutate('User', 'update', {
|
|
59
|
+
* id: 'user-id',
|
|
60
|
+
* status: 'deleted',
|
|
61
|
+
* deletedAt: new Date().toISOString(),
|
|
62
|
+
* });
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @example Delete
|
|
66
|
+
* ```typescript
|
|
67
|
+
* const result = await mutate('User', 'delete', { id: 'user-id' });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export async function mutate<T = any>(
|
|
71
|
+
model: string,
|
|
72
|
+
operation: "create" | "update" | "delete",
|
|
73
|
+
data: Record<string, any>,
|
|
74
|
+
options?: ProxyOptions,
|
|
75
|
+
): Promise<GraphQLResponse<T>> {
|
|
76
|
+
return proxyRequest<T>(model, operation, data, options);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Execute a GraphQL query through the server proxy
|
|
81
|
+
*
|
|
82
|
+
* @param model - The model name (e.g., 'User', 'Brand', 'Account')
|
|
83
|
+
* @param operation - The operation type ('get', 'list')
|
|
84
|
+
* @param data - The query parameters (id for get, filter/limit for list)
|
|
85
|
+
* @param options - Optional configuration
|
|
86
|
+
* @returns Promise with the query result
|
|
87
|
+
*
|
|
88
|
+
* @example Get by ID
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const result = await query('User', 'get', { id: 'user-id' });
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @example List with filter
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const result = await query('User', 'list', {
|
|
96
|
+
* filter: { status: { eq: 'active' } },
|
|
97
|
+
* limit: 100,
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export async function query<T = any>(
|
|
102
|
+
model: string,
|
|
103
|
+
operation: "get" | "list",
|
|
104
|
+
data?: Record<string, any>,
|
|
105
|
+
options?: ProxyOptions,
|
|
106
|
+
): Promise<GraphQLResponse<T>> {
|
|
107
|
+
return proxyRequest<T>(model, operation, data, options);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Internal function to make proxy requests
|
|
112
|
+
*/
|
|
113
|
+
async function proxyRequest<T>(
|
|
114
|
+
model: string,
|
|
115
|
+
operation: Operation,
|
|
116
|
+
data?: Record<string, any>,
|
|
117
|
+
options?: ProxyOptions,
|
|
118
|
+
): Promise<GraphQLResponse<T>> {
|
|
119
|
+
const endpoint = options?.endpoint ?? DEFAULT_ENDPOINT;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const response = await fetch(endpoint, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: {
|
|
125
|
+
"Content-Type": "application/json",
|
|
126
|
+
},
|
|
127
|
+
credentials: "include", // Important: include cookies
|
|
128
|
+
...options?.fetchOptions,
|
|
129
|
+
body: JSON.stringify({ model, operation, data }),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const result: GraphQLResponse<T> = await response.json();
|
|
133
|
+
|
|
134
|
+
// Handle auth errors
|
|
135
|
+
if (response.status === 401) {
|
|
136
|
+
console.error("[GraphQL Proxy] Unauthorized - session may have expired");
|
|
137
|
+
// Optionally trigger a redirect to login
|
|
138
|
+
// window.location.href = '/login';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return result;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error("[GraphQL Proxy] Request failed:", error);
|
|
144
|
+
return {
|
|
145
|
+
data: null,
|
|
146
|
+
errors: [
|
|
147
|
+
{
|
|
148
|
+
message: error instanceof Error ? error.message : "Network error",
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Helper to check if a response has errors
|
|
157
|
+
*/
|
|
158
|
+
export function hasErrors(response: GraphQLResponse): boolean {
|
|
159
|
+
return !!response.errors && response.errors.length > 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Helper to get the first error message from a response
|
|
164
|
+
*/
|
|
165
|
+
export function getErrorMessage(response: GraphQLResponse): string | null {
|
|
166
|
+
if (!response.errors || response.errors.length === 0) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return response.errors[0].message;
|
|
170
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,17 @@ export {
|
|
|
16
16
|
type AstroAmplifyConfig,
|
|
17
17
|
} from "./client";
|
|
18
18
|
|
|
19
|
+
// Proxy exports (for authenticated client-side operations)
|
|
20
|
+
export {
|
|
21
|
+
mutate,
|
|
22
|
+
query,
|
|
23
|
+
hasErrors,
|
|
24
|
+
getErrorMessage,
|
|
25
|
+
type Operation,
|
|
26
|
+
type GraphQLResponse,
|
|
27
|
+
type ProxyOptions,
|
|
28
|
+
} from "./client";
|
|
29
|
+
|
|
19
30
|
// Query exports
|
|
20
31
|
export * from "./queries";
|
|
21
32
|
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Account } 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 an account
|
|
@@ -96,7 +100,104 @@ export async function updateAccount<TClient = any>(
|
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
/**
|
|
99
|
-
*
|
|
103
|
+
* Soft delete an account (sets status to "deleted" instead of removing)
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* import { softDeleteAccount } from '@htlkg/data/mutations';
|
|
108
|
+
* import { generateClient } from '@htlkg/data/client';
|
|
109
|
+
*
|
|
110
|
+
* const client = generateClient<Schema>();
|
|
111
|
+
* await softDeleteAccount(client, 'account-123', 'admin@example.com');
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export async function softDeleteAccount<TClient = any>(
|
|
115
|
+
client: TClient,
|
|
116
|
+
id: string,
|
|
117
|
+
deletedBy: string,
|
|
118
|
+
): Promise<boolean> {
|
|
119
|
+
try {
|
|
120
|
+
const { errors } = await (client as any).models.Account.update({
|
|
121
|
+
id,
|
|
122
|
+
status: "deleted",
|
|
123
|
+
deletedAt: new Date().toISOString(),
|
|
124
|
+
deletedBy,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (errors) {
|
|
128
|
+
console.error("[softDeleteAccount] GraphQL errors:", errors);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return true;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error("[softDeleteAccount] Error soft-deleting account:", error);
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Restore a soft-deleted account (sets status back to "active")
|
|
141
|
+
* Checks retention period before allowing restoration
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* import { restoreAccount } from '@htlkg/data/mutations';
|
|
146
|
+
* import { generateClient } from '@htlkg/data/client';
|
|
147
|
+
*
|
|
148
|
+
* const client = generateClient<Schema>();
|
|
149
|
+
* await restoreAccount(client, 'account-123');
|
|
150
|
+
* // Or with custom retention days:
|
|
151
|
+
* await restoreAccount(client, 'account-123', 60);
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
export async function restoreAccount<TClient = any>(
|
|
155
|
+
client: TClient,
|
|
156
|
+
id: string,
|
|
157
|
+
retentionDays: number = DEFAULT_SOFT_DELETE_RETENTION_DAYS,
|
|
158
|
+
): Promise<{ success: boolean; error?: string }> {
|
|
159
|
+
try {
|
|
160
|
+
// First, get the account to check its deletedAt timestamp
|
|
161
|
+
const { data: account, errors: getErrors } = await (
|
|
162
|
+
client as any
|
|
163
|
+
).models.Account.get({ id });
|
|
164
|
+
|
|
165
|
+
if (getErrors || !account) {
|
|
166
|
+
console.error("[restoreAccount] Error fetching account:", getErrors);
|
|
167
|
+
return { success: false, error: "Account not found" };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check if restoration is allowed based on retention period
|
|
171
|
+
const eligibility = checkRestoreEligibility(account.deletedAt, retentionDays);
|
|
172
|
+
|
|
173
|
+
if (!eligibility.canRestore) {
|
|
174
|
+
const errorMsg = `Cannot restore account. Retention period of ${retentionDays} days has expired. Item was deleted ${eligibility.daysExpired} days ago.`;
|
|
175
|
+
console.error("[restoreAccount]", errorMsg);
|
|
176
|
+
return { success: false, error: errorMsg };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const { errors } = await (client as any).models.Account.update({
|
|
180
|
+
id,
|
|
181
|
+
status: "active",
|
|
182
|
+
deletedAt: null,
|
|
183
|
+
deletedBy: null,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (errors) {
|
|
187
|
+
console.error("[restoreAccount] GraphQL errors:", errors);
|
|
188
|
+
return { success: false, error: "Failed to restore account" };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return { success: true };
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error("[restoreAccount] Error restoring account:", error);
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Hard delete an account (permanently removes from database)
|
|
200
|
+
* Use with caution - prefer softDeleteAccount for recoverable deletion
|
|
100
201
|
*
|
|
101
202
|
* @example
|
|
102
203
|
* ```typescript
|
package/src/mutations/brands.ts
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Brand } 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 brand
|
|
@@ -102,7 +106,104 @@ export async function updateBrand<TClient = any>(
|
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
/**
|
|
105
|
-
*
|
|
109
|
+
* Soft delete a brand (sets status to "deleted" instead of removing)
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* import { softDeleteBrand } from '@htlkg/data/mutations';
|
|
114
|
+
* import { generateClient } from '@htlkg/data/client';
|
|
115
|
+
*
|
|
116
|
+
* const client = generateClient<Schema>();
|
|
117
|
+
* await softDeleteBrand(client, 'brand-123', 'admin@example.com');
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export async function softDeleteBrand<TClient = any>(
|
|
121
|
+
client: TClient,
|
|
122
|
+
id: string,
|
|
123
|
+
deletedBy: string,
|
|
124
|
+
): Promise<boolean> {
|
|
125
|
+
try {
|
|
126
|
+
const { errors } = await (client as any).models.Brand.update({
|
|
127
|
+
id,
|
|
128
|
+
status: "deleted",
|
|
129
|
+
deletedAt: new Date().toISOString(),
|
|
130
|
+
deletedBy,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (errors) {
|
|
134
|
+
console.error("[softDeleteBrand] GraphQL errors:", errors);
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return true;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error("[softDeleteBrand] Error soft-deleting brand:", error);
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Restore a soft-deleted brand (sets status back to "active")
|
|
147
|
+
* Checks retention period before allowing restoration
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* import { restoreBrand } from '@htlkg/data/mutations';
|
|
152
|
+
* import { generateClient } from '@htlkg/data/client';
|
|
153
|
+
*
|
|
154
|
+
* const client = generateClient<Schema>();
|
|
155
|
+
* await restoreBrand(client, 'brand-123');
|
|
156
|
+
* // Or with custom retention days:
|
|
157
|
+
* await restoreBrand(client, 'brand-123', 60);
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export async function restoreBrand<TClient = any>(
|
|
161
|
+
client: TClient,
|
|
162
|
+
id: string,
|
|
163
|
+
retentionDays: number = DEFAULT_SOFT_DELETE_RETENTION_DAYS,
|
|
164
|
+
): Promise<{ success: boolean; error?: string }> {
|
|
165
|
+
try {
|
|
166
|
+
// First, get the brand to check its deletedAt timestamp
|
|
167
|
+
const { data: brand, errors: getErrors } = await (
|
|
168
|
+
client as any
|
|
169
|
+
).models.Brand.get({ id });
|
|
170
|
+
|
|
171
|
+
if (getErrors || !brand) {
|
|
172
|
+
console.error("[restoreBrand] Error fetching brand:", getErrors);
|
|
173
|
+
return { success: false, error: "Brand not found" };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Check if restoration is allowed based on retention period
|
|
177
|
+
const eligibility = checkRestoreEligibility(brand.deletedAt, retentionDays);
|
|
178
|
+
|
|
179
|
+
if (!eligibility.canRestore) {
|
|
180
|
+
const errorMsg = `Cannot restore brand. Retention period of ${retentionDays} days has expired. Item was deleted ${eligibility.daysExpired} days ago.`;
|
|
181
|
+
console.error("[restoreBrand]", errorMsg);
|
|
182
|
+
return { success: false, error: errorMsg };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const { errors } = await (client as any).models.Brand.update({
|
|
186
|
+
id,
|
|
187
|
+
status: "active",
|
|
188
|
+
deletedAt: null,
|
|
189
|
+
deletedBy: null,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (errors) {
|
|
193
|
+
console.error("[restoreBrand] GraphQL errors:", errors);
|
|
194
|
+
return { success: false, error: "Failed to restore brand" };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { success: true };
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error("[restoreBrand] Error restoring brand:", error);
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Hard delete a brand (permanently removes from database)
|
|
206
|
+
* Use with caution - prefer softDeleteBrand for recoverable deletion
|
|
106
207
|
*
|
|
107
208
|
* @example
|
|
108
209
|
* ```typescript
|
package/src/mutations/index.ts
CHANGED
|
@@ -9,6 +9,8 @@ export {
|
|
|
9
9
|
createBrand,
|
|
10
10
|
updateBrand,
|
|
11
11
|
deleteBrand,
|
|
12
|
+
softDeleteBrand,
|
|
13
|
+
restoreBrand,
|
|
12
14
|
type CreateBrandInput,
|
|
13
15
|
type UpdateBrandInput,
|
|
14
16
|
} from "./brands";
|
|
@@ -18,6 +20,8 @@ export {
|
|
|
18
20
|
createAccount,
|
|
19
21
|
updateAccount,
|
|
20
22
|
deleteAccount,
|
|
23
|
+
softDeleteAccount,
|
|
24
|
+
restoreAccount,
|
|
21
25
|
type CreateAccountInput,
|
|
22
26
|
type UpdateAccountInput,
|
|
23
27
|
} from "./accounts";
|
|
@@ -27,6 +31,8 @@ export {
|
|
|
27
31
|
createUser,
|
|
28
32
|
updateUser,
|
|
29
33
|
deleteUser,
|
|
34
|
+
softDeleteUser,
|
|
35
|
+
restoreUser,
|
|
30
36
|
type CreateUserInput,
|
|
31
37
|
type UpdateUserInput,
|
|
32
38
|
} from "./users";
|
|
@@ -40,3 +46,10 @@ export {
|
|
|
40
46
|
type CreateProductInstanceInput,
|
|
41
47
|
type UpdateProductInstanceInput,
|
|
42
48
|
} from "./productInstances";
|
|
49
|
+
|
|
50
|
+
// SystemSettings mutations
|
|
51
|
+
export {
|
|
52
|
+
updateSystemSettings,
|
|
53
|
+
initializeSystemSettings,
|
|
54
|
+
type UpdateSystemSettingsInput,
|
|
55
|
+
} from "./systemSettings";
|
|
@@ -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
|
+
}
|
package/src/mutations/users.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|