@friggframework/admin-scripts 2.0.0--canary.522.cbd3d5a.0 → 2.0.0--canary.517.35ee143.0
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/index.js +2 -2
- package/package.json +6 -9
- package/src/application/__tests__/admin-frigg-commands.test.js +19 -19
- package/src/application/__tests__/admin-script-base.test.js +2 -2
- package/src/application/__tests__/script-runner.test.js +146 -16
- package/src/application/admin-frigg-commands.js +8 -8
- package/src/application/admin-script-base.js +3 -5
- package/src/application/script-runner.js +125 -129
- package/src/application/use-cases/__tests__/delete-schedule-use-case.test.js +168 -0
- package/src/application/use-cases/__tests__/get-effective-schedule-use-case.test.js +114 -0
- package/src/application/use-cases/__tests__/upsert-schedule-use-case.test.js +201 -0
- package/src/application/use-cases/delete-schedule-use-case.js +108 -0
- package/src/application/use-cases/get-effective-schedule-use-case.js +78 -0
- package/src/application/use-cases/index.js +18 -0
- package/src/application/use-cases/upsert-schedule-use-case.js +127 -0
- package/src/builtins/__tests__/integration-health-check.test.js +1 -1
- package/src/builtins/__tests__/oauth-token-refresh.test.js +1 -1
- package/src/builtins/integration-health-check.js +1 -1
- package/src/builtins/oauth-token-refresh.js +1 -1
- package/src/infrastructure/__tests__/admin-auth-middleware.test.js +32 -95
- package/src/infrastructure/__tests__/admin-script-router.test.js +46 -47
- package/src/infrastructure/admin-auth-middleware.js +5 -43
- package/src/infrastructure/admin-script-router.js +38 -32
- package/src/infrastructure/script-executor-handler.js +2 -2
- package/src/application/__tests__/dry-run-http-interceptor.test.js +0 -313
- package/src/application/__tests__/dry-run-repository-wrapper.test.js +0 -257
- package/src/application/__tests__/schedule-management-use-case.test.js +0 -276
- package/src/application/dry-run-http-interceptor.js +0 -296
- package/src/application/dry-run-repository-wrapper.js +0 -261
- package/src/application/schedule-management-use-case.js +0 -230
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Schedule Management Use Case
|
|
3
|
-
*
|
|
4
|
-
* Application Layer - Hexagonal Architecture
|
|
5
|
-
*
|
|
6
|
-
* Orchestrates schedule management operations:
|
|
7
|
-
* - Get effective schedule (DB override > Definition > none)
|
|
8
|
-
* - Upsert schedule with EventBridge provisioning
|
|
9
|
-
* - Delete schedule with EventBridge cleanup
|
|
10
|
-
*
|
|
11
|
-
* This use case encapsulates the business logic that was previously
|
|
12
|
-
* embedded in the router, reducing cognitive complexity and improving testability.
|
|
13
|
-
*/
|
|
14
|
-
class ScheduleManagementUseCase {
|
|
15
|
-
constructor({ commands, schedulerAdapter, scriptFactory }) {
|
|
16
|
-
this.commands = commands;
|
|
17
|
-
this.schedulerAdapter = schedulerAdapter;
|
|
18
|
-
this.scriptFactory = scriptFactory;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Validate that a script exists
|
|
23
|
-
* @private
|
|
24
|
-
*/
|
|
25
|
-
_validateScriptExists(scriptName) {
|
|
26
|
-
if (!this.scriptFactory.has(scriptName)) {
|
|
27
|
-
const error = new Error(`Script "${scriptName}" not found`);
|
|
28
|
-
error.code = 'SCRIPT_NOT_FOUND';
|
|
29
|
-
throw error;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Get the definition schedule from a script class
|
|
35
|
-
* @private
|
|
36
|
-
*/
|
|
37
|
-
_getDefinitionSchedule(scriptName) {
|
|
38
|
-
const scriptClass = this.scriptFactory.get(scriptName);
|
|
39
|
-
return scriptClass.Definition?.schedule || null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Get effective schedule (DB override > Definition default > none)
|
|
44
|
-
*/
|
|
45
|
-
async getEffectiveSchedule(scriptName) {
|
|
46
|
-
this._validateScriptExists(scriptName);
|
|
47
|
-
|
|
48
|
-
// Check database override first
|
|
49
|
-
const dbSchedule = await this.commands.getScheduleByScriptName(scriptName);
|
|
50
|
-
if (dbSchedule) {
|
|
51
|
-
return {
|
|
52
|
-
source: 'database',
|
|
53
|
-
schedule: dbSchedule,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Check definition default
|
|
58
|
-
const definitionSchedule = this._getDefinitionSchedule(scriptName);
|
|
59
|
-
if (definitionSchedule?.enabled) {
|
|
60
|
-
return {
|
|
61
|
-
source: 'definition',
|
|
62
|
-
schedule: {
|
|
63
|
-
scriptName,
|
|
64
|
-
enabled: definitionSchedule.enabled,
|
|
65
|
-
cronExpression: definitionSchedule.cronExpression,
|
|
66
|
-
timezone: definitionSchedule.timezone || 'UTC',
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// No schedule configured
|
|
72
|
-
return {
|
|
73
|
-
source: 'none',
|
|
74
|
-
schedule: {
|
|
75
|
-
scriptName,
|
|
76
|
-
enabled: false,
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Create or update schedule with EventBridge provisioning
|
|
83
|
-
*/
|
|
84
|
-
async upsertSchedule(scriptName, { enabled, cronExpression, timezone }) {
|
|
85
|
-
this._validateScriptExists(scriptName);
|
|
86
|
-
this._validateScheduleInput(enabled, cronExpression);
|
|
87
|
-
|
|
88
|
-
// Save to database
|
|
89
|
-
const schedule = await this.commands.upsertSchedule({
|
|
90
|
-
scriptName,
|
|
91
|
-
enabled,
|
|
92
|
-
cronExpression: cronExpression || null,
|
|
93
|
-
timezone: timezone || 'UTC',
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Provision/deprovision EventBridge
|
|
97
|
-
const schedulerResult = await this._syncEventBridgeSchedule(
|
|
98
|
-
scriptName,
|
|
99
|
-
enabled,
|
|
100
|
-
cronExpression,
|
|
101
|
-
timezone,
|
|
102
|
-
schedule.awsScheduleArn
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
success: true,
|
|
107
|
-
schedule: {
|
|
108
|
-
...schedule,
|
|
109
|
-
awsScheduleArn: schedulerResult.awsScheduleArn || schedule.awsScheduleArn,
|
|
110
|
-
awsScheduleName: schedulerResult.awsScheduleName || schedule.awsScheduleName,
|
|
111
|
-
},
|
|
112
|
-
...(schedulerResult.warning && { schedulerWarning: schedulerResult.warning }),
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Validate schedule input
|
|
118
|
-
* @private
|
|
119
|
-
*/
|
|
120
|
-
_validateScheduleInput(enabled, cronExpression) {
|
|
121
|
-
if (typeof enabled !== 'boolean') {
|
|
122
|
-
const error = new Error('enabled must be a boolean');
|
|
123
|
-
error.code = 'INVALID_INPUT';
|
|
124
|
-
throw error;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (enabled && !cronExpression) {
|
|
128
|
-
const error = new Error('cronExpression is required when enabled is true');
|
|
129
|
-
error.code = 'INVALID_INPUT';
|
|
130
|
-
throw error;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Sync EventBridge schedule based on enabled state
|
|
136
|
-
* @private
|
|
137
|
-
*/
|
|
138
|
-
async _syncEventBridgeSchedule(scriptName, enabled, cronExpression, timezone, existingArn) {
|
|
139
|
-
const result = { awsScheduleArn: null, awsScheduleName: null, warning: null };
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
if (enabled && cronExpression) {
|
|
143
|
-
// Create/update EventBridge schedule
|
|
144
|
-
const awsInfo = await this.schedulerAdapter.createSchedule({
|
|
145
|
-
scriptName,
|
|
146
|
-
cronExpression,
|
|
147
|
-
timezone: timezone || 'UTC',
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
if (awsInfo?.scheduleArn) {
|
|
151
|
-
await this.commands.updateScheduleAwsInfo(scriptName, {
|
|
152
|
-
awsScheduleArn: awsInfo.scheduleArn,
|
|
153
|
-
awsScheduleName: awsInfo.scheduleName,
|
|
154
|
-
});
|
|
155
|
-
result.awsScheduleArn = awsInfo.scheduleArn;
|
|
156
|
-
result.awsScheduleName = awsInfo.scheduleName;
|
|
157
|
-
}
|
|
158
|
-
} else if (!enabled && existingArn) {
|
|
159
|
-
// Delete EventBridge schedule
|
|
160
|
-
await this.schedulerAdapter.deleteSchedule(scriptName);
|
|
161
|
-
await this.commands.updateScheduleAwsInfo(scriptName, {
|
|
162
|
-
awsScheduleArn: null,
|
|
163
|
-
awsScheduleName: null,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
} catch (error) {
|
|
167
|
-
// Non-fatal: DB schedule is saved, AWS can be retried
|
|
168
|
-
result.warning = error.message;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return result;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Delete schedule override and cleanup EventBridge
|
|
176
|
-
*/
|
|
177
|
-
async deleteSchedule(scriptName) {
|
|
178
|
-
this._validateScriptExists(scriptName);
|
|
179
|
-
|
|
180
|
-
// Delete from database
|
|
181
|
-
const deleteResult = await this.commands.deleteSchedule(scriptName);
|
|
182
|
-
|
|
183
|
-
// Cleanup EventBridge if needed
|
|
184
|
-
const schedulerWarning = await this._cleanupEventBridgeSchedule(
|
|
185
|
-
scriptName,
|
|
186
|
-
deleteResult.deleted?.awsScheduleArn
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
// Get effective schedule after deletion
|
|
190
|
-
const definitionSchedule = this._getDefinitionSchedule(scriptName);
|
|
191
|
-
const effectiveSchedule = definitionSchedule?.enabled
|
|
192
|
-
? {
|
|
193
|
-
source: 'definition',
|
|
194
|
-
enabled: definitionSchedule.enabled,
|
|
195
|
-
cronExpression: definitionSchedule.cronExpression,
|
|
196
|
-
timezone: definitionSchedule.timezone || 'UTC',
|
|
197
|
-
}
|
|
198
|
-
: { source: 'none', enabled: false };
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
success: true,
|
|
202
|
-
deletedCount: deleteResult.deletedCount,
|
|
203
|
-
message: deleteResult.deletedCount > 0
|
|
204
|
-
? 'Schedule override removed'
|
|
205
|
-
: 'No schedule override found',
|
|
206
|
-
effectiveSchedule,
|
|
207
|
-
...(schedulerWarning && { schedulerWarning }),
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Cleanup EventBridge schedule if it exists
|
|
213
|
-
* @private
|
|
214
|
-
*/
|
|
215
|
-
async _cleanupEventBridgeSchedule(scriptName, awsScheduleArn) {
|
|
216
|
-
if (!awsScheduleArn) {
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
try {
|
|
221
|
-
await this.schedulerAdapter.deleteSchedule(scriptName);
|
|
222
|
-
return null;
|
|
223
|
-
} catch (error) {
|
|
224
|
-
// Non-fatal: DB is cleaned up, AWS can be retried
|
|
225
|
-
return error.message;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
module.exports = { ScheduleManagementUseCase };
|