@friggframework/admin-scripts 2.0.0--canary.517.41839c5.0 → 2.0.0--canary.517.a37d697.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 CHANGED
@@ -12,7 +12,7 @@ const { AdminFriggCommands, createAdminFriggCommands } = require('./src/applicat
12
12
  const { ScriptRunner, createScriptRunner } = require('./src/application/script-runner');
13
13
 
14
14
  // Infrastructure
15
- const { adminAuthMiddleware } = require('./src/infrastructure/admin-auth-middleware');
15
+ const { validateAdminApiKey } = require('./src/infrastructure/admin-auth-middleware');
16
16
  const { router, app, handler: routerHandler } = require('./src/infrastructure/admin-script-router');
17
17
  const { handler: executorHandler } = require('./src/infrastructure/script-executor-handler');
18
18
 
@@ -45,7 +45,7 @@ module.exports = {
45
45
  createScriptRunner,
46
46
 
47
47
  // Infrastructure layer
48
- adminAuthMiddleware,
48
+ validateAdminApiKey,
49
49
  router,
50
50
  app,
51
51
  routerHandler,
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@friggframework/admin-scripts",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.517.41839c5.0",
4
+ "version": "2.0.0--canary.517.a37d697.0",
5
5
  "description": "Admin Script Runner for Frigg - Execute maintenance and operational scripts in hosted environments",
6
6
  "dependencies": {
7
7
  "@aws-sdk/client-scheduler": "^3.588.0",
8
- "@friggframework/core": "2.0.0--canary.517.41839c5.0",
8
+ "@friggframework/core": "2.0.0--canary.517.a37d697.0",
9
9
  "bcryptjs": "^2.4.3",
10
10
  "express": "^4.18.2",
11
11
  "lodash": "4.17.21",
@@ -14,9 +14,9 @@
14
14
  "uuid": "^9.0.1"
15
15
  },
16
16
  "devDependencies": {
17
- "@friggframework/eslint-config": "2.0.0--canary.517.41839c5.0",
18
- "@friggframework/prettier-config": "2.0.0--canary.517.41839c5.0",
19
- "@friggframework/test": "2.0.0--canary.517.41839c5.0",
17
+ "@friggframework/eslint-config": "2.0.0--canary.517.a37d697.0",
18
+ "@friggframework/prettier-config": "2.0.0--canary.517.a37d697.0",
19
+ "@friggframework/test": "2.0.0--canary.517.a37d697.0",
20
20
  "chai": "^4.3.6",
21
21
  "eslint": "^8.22.0",
22
22
  "jest": "^29.7.0",
@@ -49,5 +49,5 @@
49
49
  "maintenance",
50
50
  "operations"
51
51
  ],
52
- "gitHead": "41839c5520f3b4a0991017021e54596bbb8c01da"
52
+ "gitHead": "a37d697613667fff814938b1f30ee2d834c4ffcd"
53
53
  }
@@ -5,7 +5,7 @@ jest.mock('@friggframework/core/integrations/repositories/integration-repository
5
5
  jest.mock('@friggframework/core/user/repositories/user-repository-factory');
6
6
  jest.mock('@friggframework/core/modules/repositories/module-repository-factory');
7
7
  jest.mock('@friggframework/core/credential/repositories/credential-repository-factory');
8
- jest.mock('@friggframework/core/admin-scripts/repositories/script-execution-repository-factory');
8
+ jest.mock('@friggframework/core/admin-scripts/repositories/admin-process-repository-factory');
9
9
  jest.mock('@friggframework/core/queues');
10
10
 
11
11
  describe('AdminFriggCommands', () => {
@@ -13,7 +13,7 @@ describe('AdminFriggCommands', () => {
13
13
  let mockUserRepo;
14
14
  let mockModuleRepo;
15
15
  let mockCredentialRepo;
16
- let mockScriptExecutionRepo;
16
+ let mockAdminProcessRepo;
17
17
  let mockQueuerUtil;
18
18
 
19
19
  beforeEach(() => {
@@ -46,8 +46,8 @@ describe('AdminFriggCommands', () => {
46
46
  updateCredential: jest.fn(),
47
47
  };
48
48
 
49
- mockScriptExecutionRepo = {
50
- appendExecutionLog: jest.fn().mockResolvedValue(undefined),
49
+ mockAdminProcessRepo = {
50
+ appendProcessLog: jest.fn().mockResolvedValue(undefined),
51
51
  };
52
52
 
53
53
  mockQueuerUtil = {
@@ -60,14 +60,14 @@ describe('AdminFriggCommands', () => {
60
60
  const { createUserRepository } = require('@friggframework/core/user/repositories/user-repository-factory');
61
61
  const { createModuleRepository } = require('@friggframework/core/modules/repositories/module-repository-factory');
62
62
  const { createCredentialRepository } = require('@friggframework/core/credential/repositories/credential-repository-factory');
63
- const { createScriptExecutionRepository } = require('@friggframework/core/admin-scripts/repositories/script-execution-repository-factory');
63
+ const { createAdminProcessRepository } = require('@friggframework/core/admin-scripts/repositories/admin-process-repository-factory');
64
64
  const { QueuerUtil } = require('@friggframework/core/queues');
65
65
 
66
66
  createIntegrationRepository.mockReturnValue(mockIntegrationRepo);
67
67
  createUserRepository.mockReturnValue(mockUserRepo);
68
68
  createModuleRepository.mockReturnValue(mockModuleRepo);
69
69
  createCredentialRepository.mockReturnValue(mockCredentialRepo);
70
- createScriptExecutionRepository.mockReturnValue(mockScriptExecutionRepo);
70
+ createAdminProcessRepository.mockReturnValue(mockAdminProcessRepo);
71
71
 
72
72
  // Mock QueuerUtil methods
73
73
  QueuerUtil.send = mockQueuerUtil.send;
@@ -158,16 +158,16 @@ describe('AdminFriggCommands', () => {
158
158
  expect(repo).toBe(mockCredentialRepo);
159
159
  });
160
160
 
161
- it('creates scriptExecutionRepository on first access', () => {
161
+ it('creates adminProcessRepository on first access', () => {
162
162
  const commands = new AdminFriggCommands();
163
- const { createScriptExecutionRepository } = require('@friggframework/core/admin-scripts/repositories/script-execution-repository-factory');
163
+ const { createAdminProcessRepository } = require('@friggframework/core/admin-scripts/repositories/admin-process-repository-factory');
164
164
 
165
- expect(createScriptExecutionRepository).not.toHaveBeenCalled();
165
+ expect(createAdminProcessRepository).not.toHaveBeenCalled();
166
166
 
167
- const repo = commands.scriptExecutionRepository;
167
+ const repo = commands.adminProcessRepository;
168
168
 
169
- expect(createScriptExecutionRepository).toHaveBeenCalledTimes(1);
170
- expect(repo).toBe(mockScriptExecutionRepo);
169
+ expect(createAdminProcessRepository).toHaveBeenCalledTimes(1);
170
+ expect(repo).toBe(mockAdminProcessRepo);
171
171
  });
172
172
  });
173
173
 
@@ -551,15 +551,15 @@ describe('AdminFriggCommands', () => {
551
551
  it('log() persists if executionId set', async () => {
552
552
  const commands = new AdminFriggCommands({ executionId: 'exec_123' });
553
553
  // Force repository creation
554
- commands.scriptExecutionRepository;
554
+ commands.adminProcessRepository;
555
555
 
556
556
  commands.log('warn', 'Warning message', { detail: 'xyz' });
557
557
 
558
558
  // Give async operation a chance to execute
559
559
  await new Promise(resolve => setImmediate(resolve));
560
560
 
561
- expect(mockScriptExecutionRepo.appendExecutionLog).toHaveBeenCalled();
562
- const callArgs = mockScriptExecutionRepo.appendExecutionLog.mock.calls[0];
561
+ expect(mockAdminProcessRepo.appendProcessLog).toHaveBeenCalled();
562
+ const callArgs = mockAdminProcessRepo.appendProcessLog.mock.calls[0];
563
563
  expect(callArgs[0]).toBe('exec_123');
564
564
  expect(callArgs[1].level).toBe('warn');
565
565
  expect(callArgs[1].message).toBe('Warning message');
@@ -572,14 +572,14 @@ describe('AdminFriggCommands', () => {
572
572
 
573
573
  await new Promise(resolve => setImmediate(resolve));
574
574
 
575
- expect(mockScriptExecutionRepo.appendExecutionLog).not.toHaveBeenCalled();
575
+ expect(mockAdminProcessRepo.appendProcessLog).not.toHaveBeenCalled();
576
576
  });
577
577
 
578
578
  it('log() handles persistence failure gracefully', async () => {
579
579
  const commands = new AdminFriggCommands({ executionId: 'exec_123' });
580
580
  // Force repository creation
581
- commands.scriptExecutionRepository;
582
- mockScriptExecutionRepo.appendExecutionLog.mockRejectedValue(new Error('DB Error'));
581
+ commands.adminProcessRepository;
582
+ mockAdminProcessRepo.appendProcessLog.mockRejectedValue(new Error('DB Error'));
583
583
 
584
584
  // Should not throw
585
585
  expect(() => commands.log('error', 'Test error')).not.toThrow();
@@ -36,9 +36,9 @@ describe('ScriptRunner', () => {
36
36
  scriptFactory = new ScriptFactory([TestScript]);
37
37
 
38
38
  mockCommands = {
39
- createScriptExecution: jest.fn(),
40
- updateScriptExecutionStatus: jest.fn(),
41
- completeScriptExecution: jest.fn(),
39
+ createAdminProcess: jest.fn(),
40
+ updateAdminProcessState: jest.fn(),
41
+ completeAdminProcess: jest.fn(),
42
42
  };
43
43
 
44
44
  mockFrigg = {
@@ -49,11 +49,11 @@ describe('ScriptRunner', () => {
49
49
  createAdminScriptCommands.mockReturnValue(mockCommands);
50
50
  createAdminFriggCommands.mockReturnValue(mockFrigg);
51
51
 
52
- mockCommands.createScriptExecution.mockResolvedValue({
52
+ mockCommands.createAdminProcess.mockResolvedValue({
53
53
  id: 'exec-123',
54
54
  });
55
- mockCommands.updateScriptExecutionStatus.mockResolvedValue({});
56
- mockCommands.completeScriptExecution.mockResolvedValue({ success: true });
55
+ mockCommands.updateAdminProcessState.mockResolvedValue({});
56
+ mockCommands.completeAdminProcess.mockResolvedValue({ success: true });
57
57
  });
58
58
 
59
59
  afterEach(() => {
@@ -76,7 +76,7 @@ describe('ScriptRunner', () => {
76
76
  expect(result.executionId).toBe('exec-123');
77
77
  expect(result.metrics.durationMs).toBeGreaterThanOrEqual(0);
78
78
 
79
- expect(mockCommands.createScriptExecution).toHaveBeenCalledWith({
79
+ expect(mockCommands.createAdminProcess).toHaveBeenCalledWith({
80
80
  scriptName: 'test-script',
81
81
  scriptVersion: '1.0.0',
82
82
  trigger: 'MANUAL',
@@ -85,12 +85,12 @@ describe('ScriptRunner', () => {
85
85
  audit: { apiKeyName: 'test-key' },
86
86
  });
87
87
 
88
- expect(mockCommands.updateScriptExecutionStatus).toHaveBeenCalledWith(
88
+ expect(mockCommands.updateAdminProcessState).toHaveBeenCalledWith(
89
89
  'exec-123',
90
90
  'RUNNING'
91
91
  );
92
92
 
93
- expect(mockCommands.completeScriptExecution).toHaveBeenCalledWith(
93
+ expect(mockCommands.completeAdminProcess).toHaveBeenCalledWith(
94
94
  'exec-123',
95
95
  expect.objectContaining({
96
96
  status: 'COMPLETED',
@@ -128,7 +128,7 @@ describe('ScriptRunner', () => {
128
128
  expect(result.scriptName).toBe('failing-script');
129
129
  expect(result.error.message).toBe('Script failed');
130
130
 
131
- expect(mockCommands.completeScriptExecution).toHaveBeenCalledWith(
131
+ expect(mockCommands.completeAdminProcess).toHaveBeenCalledWith(
132
132
  'exec-123',
133
133
  expect.objectContaining({
134
134
  status: 'FAILED',
@@ -178,8 +178,8 @@ describe('ScriptRunner', () => {
178
178
  });
179
179
 
180
180
  expect(result.executionId).toBe('existing-exec-456');
181
- expect(mockCommands.createScriptExecution).not.toHaveBeenCalled();
182
- expect(mockCommands.updateScriptExecutionStatus).toHaveBeenCalledWith(
181
+ expect(mockCommands.createAdminProcess).not.toHaveBeenCalled();
182
+ expect(mockCommands.updateAdminProcessState).toHaveBeenCalledWith(
183
183
  'existing-exec-456',
184
184
  'RUNNING'
185
185
  );
@@ -25,7 +25,7 @@ class AdminFriggCommands {
25
25
  this._userRepository = null;
26
26
  this._moduleRepository = null;
27
27
  this._credentialRepository = null;
28
- this._scriptExecutionRepository = null;
28
+ this._adminProcessRepository = null;
29
29
  }
30
30
 
31
31
  // ==================== LAZY-LOADED REPOSITORIES ====================
@@ -62,12 +62,12 @@ class AdminFriggCommands {
62
62
  return this._credentialRepository;
63
63
  }
64
64
 
65
- get scriptExecutionRepository() {
66
- if (!this._scriptExecutionRepository) {
67
- const { createScriptExecutionRepository } = require('@friggframework/core/admin-scripts/repositories/script-execution-repository-factory');
68
- this._scriptExecutionRepository = createScriptExecutionRepository();
65
+ get adminProcessRepository() {
66
+ if (!this._adminProcessRepository) {
67
+ const { createAdminProcessRepository } = require('@friggframework/core/admin-scripts/repositories/admin-process-repository-factory');
68
+ this._adminProcessRepository = createAdminProcessRepository();
69
69
  }
70
- return this._scriptExecutionRepository;
70
+ return this._adminProcessRepository;
71
71
  }
72
72
 
73
73
  // ==================== INTEGRATION QUERIES ====================
@@ -209,7 +209,7 @@ class AdminFriggCommands {
209
209
 
210
210
  // Persist to execution record if we have an executionId
211
211
  if (this.executionId) {
212
- this.scriptExecutionRepository.appendExecutionLog(this.executionId, entry)
212
+ this.adminProcessRepository.appendProcessLog(this.executionId, entry)
213
213
  .catch(err => console.error('Failed to persist log:', err));
214
214
  }
215
215
 
@@ -1,5 +1,4 @@
1
- const { createScriptExecutionRepository } = require('@friggframework/core/admin-scripts/repositories/script-execution-repository-factory');
2
- const { createAdminApiKeyRepository } = require('@friggframework/core/admin-scripts/repositories/admin-api-key-repository-factory');
1
+ const { createAdminProcessRepository } = require('@friggframework/core/admin-scripts/repositories/admin-process-repository-factory');
3
2
 
4
3
  /**
5
4
  * Admin Script Base Class
@@ -87,8 +86,7 @@ class AdminScriptBase {
87
86
  this.integrationFactory = params.integrationFactory || null;
88
87
 
89
88
  // OPTIONAL: Injected repositories (for testing or custom implementations)
90
- this.scriptExecutionRepository = params.scriptExecutionRepository || null;
91
- this.adminApiKeyRepository = params.adminApiKeyRepository || null;
89
+ this.adminProcessRepository = params.adminProcessRepository || null;
92
90
  }
93
91
 
94
92
  /**
@@ -50,7 +50,7 @@ class ScriptRunner {
50
50
 
51
51
  // Create execution record if not provided
52
52
  if (!executionId) {
53
- const execution = await this.commands.createScriptExecution({
53
+ const execution = await this.commands.createAdminProcess({
54
54
  scriptName,
55
55
  scriptVersion: definition.version,
56
56
  trigger,
@@ -66,7 +66,7 @@ class ScriptRunner {
66
66
  try {
67
67
  // Update status to RUNNING (skip in dry-run)
68
68
  if (!dryRun) {
69
- await this.commands.updateScriptExecutionStatus(executionId, 'RUNNING');
69
+ await this.commands.updateAdminProcessState(executionId, 'RUNNING');
70
70
  }
71
71
 
72
72
  // Create frigg commands for the script
@@ -99,7 +99,7 @@ class ScriptRunner {
99
99
 
100
100
  // Complete execution (skip in dry-run)
101
101
  if (!dryRun) {
102
- await this.commands.completeScriptExecution(executionId, {
102
+ await this.commands.completeAdminProcess(executionId, {
103
103
  status: 'COMPLETED',
104
104
  output,
105
105
  metrics: {
@@ -140,7 +140,7 @@ class ScriptRunner {
140
140
 
141
141
  // Record failure (skip in dry-run)
142
142
  if (!dryRun) {
143
- await this.commands.completeScriptExecution(executionId, {
143
+ await this.commands.completeAdminProcess(executionId, {
144
144
  status: 'FAILED',
145
145
  error: {
146
146
  name: error.name,
@@ -1,22 +1,17 @@
1
- const { adminAuthMiddleware } = require('../admin-auth-middleware');
1
+ const { validateAdminApiKey } = require('../admin-auth-middleware');
2
2
 
3
- // Mock the admin script commands
4
- jest.mock('@friggframework/core/application/commands/admin-script-commands', () => ({
5
- createAdminScriptCommands: jest.fn(),
6
- }));
7
-
8
- const { createAdminScriptCommands } = require('@friggframework/core/application/commands/admin-script-commands');
9
-
10
- describe('adminAuthMiddleware', () => {
3
+ describe('validateAdminApiKey', () => {
11
4
  let mockReq;
12
5
  let mockRes;
13
6
  let mockNext;
14
- let mockCommands;
7
+ let originalEnv;
15
8
 
16
9
  beforeEach(() => {
10
+ originalEnv = process.env.ADMIN_API_KEY;
11
+ process.env.ADMIN_API_KEY = 'test-admin-key-123';
12
+
17
13
  mockReq = {
18
14
  headers: {},
19
- ip: '127.0.0.1',
20
15
  };
21
16
 
22
17
  mockRes = {
@@ -25,124 +20,66 @@ describe('adminAuthMiddleware', () => {
25
20
  };
26
21
 
27
22
  mockNext = jest.fn();
28
-
29
- mockCommands = {
30
- validateAdminApiKey: jest.fn(),
31
- };
32
-
33
- createAdminScriptCommands.mockReturnValue(mockCommands);
34
23
  });
35
24
 
36
25
  afterEach(() => {
26
+ if (originalEnv) {
27
+ process.env.ADMIN_API_KEY = originalEnv;
28
+ } else {
29
+ delete process.env.ADMIN_API_KEY;
30
+ }
37
31
  jest.clearAllMocks();
38
32
  });
39
33
 
40
- describe('Authorization header validation', () => {
41
- it('should reject request without Authorization header', async () => {
42
- await adminAuthMiddleware(mockReq, mockRes, mockNext);
43
-
44
- expect(mockRes.status).toHaveBeenCalledWith(401);
45
- expect(mockRes.json).toHaveBeenCalledWith({
46
- error: 'Missing or invalid Authorization header',
47
- code: 'MISSING_AUTH',
48
- });
49
- expect(mockNext).not.toHaveBeenCalled();
50
- });
51
-
52
- it('should reject request with invalid Authorization format', async () => {
53
- mockReq.headers.authorization = 'InvalidFormat key123';
34
+ describe('Environment configuration', () => {
35
+ it('should reject when ADMIN_API_KEY not configured', () => {
36
+ delete process.env.ADMIN_API_KEY;
54
37
 
55
- await adminAuthMiddleware(mockReq, mockRes, mockNext);
38
+ validateAdminApiKey(mockReq, mockRes, mockNext);
56
39
 
57
40
  expect(mockRes.status).toHaveBeenCalledWith(401);
58
41
  expect(mockRes.json).toHaveBeenCalledWith({
59
- error: 'Missing or invalid Authorization header',
60
- code: 'MISSING_AUTH',
42
+ error: 'Unauthorized',
43
+ message: 'Admin API key not configured',
61
44
  });
62
45
  expect(mockNext).not.toHaveBeenCalled();
63
46
  });
64
47
  });
65
48
 
66
- describe('API key validation', () => {
67
- it('should reject request with invalid API key', async () => {
68
- mockReq.headers.authorization = 'Bearer invalid-key';
69
- mockCommands.validateAdminApiKey.mockResolvedValue({
70
- error: 401,
71
- reason: 'Invalid API key',
72
- code: 'INVALID_API_KEY',
73
- });
74
-
75
- await adminAuthMiddleware(mockReq, mockRes, mockNext);
49
+ describe('Header validation', () => {
50
+ it('should reject request without x-frigg-admin-api-key header', () => {
51
+ validateAdminApiKey(mockReq, mockRes, mockNext);
76
52
 
77
- expect(mockCommands.validateAdminApiKey).toHaveBeenCalledWith('invalid-key');
78
53
  expect(mockRes.status).toHaveBeenCalledWith(401);
79
54
  expect(mockRes.json).toHaveBeenCalledWith({
80
- error: 'Invalid API key',
81
- code: 'INVALID_API_KEY',
55
+ error: 'Unauthorized',
56
+ message: 'x-frigg-admin-api-key header required',
82
57
  });
83
58
  expect(mockNext).not.toHaveBeenCalled();
84
59
  });
60
+ });
85
61
 
86
- it('should reject request with expired API key', async () => {
87
- mockReq.headers.authorization = 'Bearer expired-key';
88
- mockCommands.validateAdminApiKey.mockResolvedValue({
89
- error: 401,
90
- reason: 'API key has expired',
91
- code: 'EXPIRED_API_KEY',
92
- });
62
+ describe('API key validation', () => {
63
+ it('should reject request with invalid API key', () => {
64
+ mockReq.headers['x-frigg-admin-api-key'] = 'invalid-key';
93
65
 
94
- await adminAuthMiddleware(mockReq, mockRes, mockNext);
66
+ validateAdminApiKey(mockReq, mockRes, mockNext);
95
67
 
96
- expect(mockCommands.validateAdminApiKey).toHaveBeenCalledWith('expired-key');
97
68
  expect(mockRes.status).toHaveBeenCalledWith(401);
98
69
  expect(mockRes.json).toHaveBeenCalledWith({
99
- error: 'API key has expired',
100
- code: 'EXPIRED_API_KEY',
70
+ error: 'Unauthorized',
71
+ message: 'Invalid admin API key',
101
72
  });
102
73
  expect(mockNext).not.toHaveBeenCalled();
103
74
  });
104
75
 
105
- it('should accept request with valid API key', async () => {
106
- const validKey = 'valid-api-key-123';
107
- mockReq.headers.authorization = `Bearer ${validKey}`;
108
- mockCommands.validateAdminApiKey.mockResolvedValue({
109
- valid: true,
110
- apiKey: {
111
- id: 'key-id-1',
112
- name: 'test-key',
113
- keyLast4: 'e123',
114
- },
115
- });
76
+ it('should accept request with valid API key', () => {
77
+ mockReq.headers['x-frigg-admin-api-key'] = 'test-admin-key-123';
116
78
 
117
- await adminAuthMiddleware(mockReq, mockRes, mockNext);
79
+ validateAdminApiKey(mockReq, mockRes, mockNext);
118
80
 
119
- expect(mockCommands.validateAdminApiKey).toHaveBeenCalledWith(validKey);
120
- expect(mockReq.adminApiKey).toBeDefined();
121
- expect(mockReq.adminApiKey.name).toBe('test-key');
122
- expect(mockReq.adminAudit).toBeDefined();
123
- expect(mockReq.adminAudit.apiKeyName).toBe('test-key');
124
- expect(mockReq.adminAudit.apiKeyLast4).toBe('e123');
125
- expect(mockReq.adminAudit.ipAddress).toBe('127.0.0.1');
126
81
  expect(mockNext).toHaveBeenCalled();
127
82
  expect(mockRes.status).not.toHaveBeenCalled();
128
83
  });
129
84
  });
130
-
131
- describe('Error handling', () => {
132
- it('should handle validation errors gracefully', async () => {
133
- mockReq.headers.authorization = 'Bearer some-key';
134
- mockCommands.validateAdminApiKey.mockRejectedValue(
135
- new Error('Database error')
136
- );
137
-
138
- await adminAuthMiddleware(mockReq, mockRes, mockNext);
139
-
140
- expect(mockRes.status).toHaveBeenCalledWith(500);
141
- expect(mockRes.json).toHaveBeenCalledWith({
142
- error: 'Authentication failed',
143
- code: 'AUTH_ERROR',
144
- });
145
- expect(mockNext).not.toHaveBeenCalled();
146
- });
147
- });
148
85
  });
@@ -4,13 +4,8 @@ const { AdminScriptBase } = require('../../application/admin-script-base');
4
4
 
5
5
  // Mock dependencies
6
6
  jest.mock('../admin-auth-middleware', () => ({
7
- adminAuthMiddleware: (req, res, next) => {
8
- // Mock auth - attach admin audit info
9
- req.adminAudit = {
10
- apiKeyName: 'test-key',
11
- apiKeyLast4: '1234',
12
- ipAddress: '127.0.0.1',
13
- };
7
+ validateAdminApiKey: (req, res, next) => {
8
+ // Mock auth - no audit trail with simplified auth
14
9
  next();
15
10
  },
16
11
  }));
@@ -59,8 +54,8 @@ describe('Admin Script Router', () => {
59
54
  };
60
55
 
61
56
  mockCommands = {
62
- createScriptExecution: jest.fn(),
63
- findScriptExecutionById: jest.fn(),
57
+ createAdminProcess: jest.fn(),
58
+ findAdminProcessById: jest.fn(),
64
59
  findRecentExecutions: jest.fn(),
65
60
  };
66
61
 
@@ -143,7 +138,7 @@ describe('Admin Script Router', () => {
143
138
  });
144
139
  });
145
140
 
146
- describe('POST /admin/scripts/:scriptName/execute', () => {
141
+ describe('POST /admin/scripts/:scriptName', () => {
147
142
  it('should execute script synchronously', async () => {
148
143
  mockRunner.execute.mockResolvedValue({
149
144
  executionId: 'exec-123',
@@ -154,7 +149,7 @@ describe('Admin Script Router', () => {
154
149
  });
155
150
 
156
151
  const response = await request(app)
157
- .post('/admin/scripts/test-script/execute')
152
+ .post('/admin/scripts/test-script')
158
153
  .send({
159
154
  params: { foo: 'bar' },
160
155
  mode: 'sync',
@@ -174,12 +169,12 @@ describe('Admin Script Router', () => {
174
169
  });
175
170
 
176
171
  it('should queue script for async execution', async () => {
177
- mockCommands.createScriptExecution.mockResolvedValue({
172
+ mockCommands.createAdminProcess.mockResolvedValue({
178
173
  id: 'exec-456',
179
174
  });
180
175
 
181
176
  const response = await request(app)
182
- .post('/admin/scripts/test-script/execute')
177
+ .post('/admin/scripts/test-script')
183
178
  .send({
184
179
  params: { foo: 'bar' },
185
180
  mode: 'async',
@@ -198,12 +193,12 @@ describe('Admin Script Router', () => {
198
193
  });
199
194
 
200
195
  it('should default to async mode', async () => {
201
- mockCommands.createScriptExecution.mockResolvedValue({
196
+ mockCommands.createAdminProcess.mockResolvedValue({
202
197
  id: 'exec-789',
203
198
  });
204
199
 
205
200
  const response = await request(app)
206
- .post('/admin/scripts/test-script/execute')
201
+ .post('/admin/scripts/test-script')
207
202
  .send({
208
203
  params: { foo: 'bar' },
209
204
  });
@@ -216,7 +211,7 @@ describe('Admin Script Router', () => {
216
211
  mockFactory.has.mockReturnValue(false);
217
212
 
218
213
  const response = await request(app)
219
- .post('/admin/scripts/non-existent/execute')
214
+ .post('/admin/scripts/non-existent')
220
215
  .send({
221
216
  params: {},
222
217
  });
@@ -226,15 +221,15 @@ describe('Admin Script Router', () => {
226
221
  });
227
222
  });
228
223
 
229
- describe('GET /admin/executions/:executionId', () => {
224
+ describe('GET /admin/scripts/:scriptName/executions/:executionId', () => {
230
225
  it('should return execution details', async () => {
231
- mockCommands.findScriptExecutionById.mockResolvedValue({
226
+ mockCommands.findAdminProcessById.mockResolvedValue({
232
227
  id: 'exec-123',
233
228
  scriptName: 'test-script',
234
229
  status: 'COMPLETED',
235
230
  });
236
231
 
237
- const response = await request(app).get('/admin/executions/exec-123');
232
+ const response = await request(app).get('/admin/scripts/test-script/executions/exec-123');
238
233
 
239
234
  expect(response.status).toBe(200);
240
235
  expect(response.body.id).toBe('exec-123');
@@ -242,14 +237,14 @@ describe('Admin Script Router', () => {
242
237
  });
243
238
 
244
239
  it('should return 404 for non-existent execution', async () => {
245
- mockCommands.findScriptExecutionById.mockResolvedValue({
240
+ mockCommands.findAdminProcessById.mockResolvedValue({
246
241
  error: 404,
247
242
  reason: 'Execution not found',
248
243
  code: 'EXECUTION_NOT_FOUND',
249
244
  });
250
245
 
251
246
  const response = await request(app).get(
252
- '/admin/executions/non-existent'
247
+ '/admin/scripts/test-script/executions/non-existent'
253
248
  );
254
249
 
255
250
  expect(response.status).toBe(404);
@@ -257,24 +252,28 @@ describe('Admin Script Router', () => {
257
252
  });
258
253
  });
259
254
 
260
- describe('GET /admin/executions', () => {
261
- it('should list recent executions', async () => {
255
+ describe('GET /admin/scripts/:scriptName/executions', () => {
256
+ it('should list executions for specific script', async () => {
262
257
  mockCommands.findRecentExecutions.mockResolvedValue([
263
258
  { id: 'exec-1', scriptName: 'test-script', status: 'COMPLETED' },
264
259
  { id: 'exec-2', scriptName: 'test-script', status: 'RUNNING' },
265
260
  ]);
266
261
 
267
- const response = await request(app).get('/admin/executions');
262
+ const response = await request(app).get('/admin/scripts/test-script/executions');
268
263
 
269
264
  expect(response.status).toBe(200);
270
265
  expect(response.body.executions).toHaveLength(2);
266
+ expect(mockCommands.findRecentExecutions).toHaveBeenCalledWith({
267
+ scriptName: 'test-script',
268
+ limit: 50,
269
+ });
271
270
  });
272
271
 
273
272
  it('should accept query parameters', async () => {
274
273
  mockCommands.findRecentExecutions.mockResolvedValue([]);
275
274
 
276
275
  await request(app).get(
277
- '/admin/executions?scriptName=test-script&status=COMPLETED&limit=10'
276
+ '/admin/scripts/test-script/executions?status=COMPLETED&limit=10'
278
277
  );
279
278
 
280
279
  expect(mockCommands.findRecentExecutions).toHaveBeenCalledWith({
@@ -1,49 +1,11 @@
1
- const { createAdminScriptCommands } = require('@friggframework/core/application/commands/admin-script-commands');
2
-
3
1
  /**
4
2
  * Admin API Key Authentication Middleware
5
3
  *
6
- * Validates admin API keys for script endpoints.
7
- * Expects: Authorization: Bearer <api-key>
4
+ * Re-exports shared admin auth middleware from @friggframework/core.
5
+ * Uses simple ENV-based API key validation.
6
+ * Expects: x-frigg-admin-api-key header
8
7
  */
9
- async function adminAuthMiddleware(req, res, next) {
10
- try {
11
- const authHeader = req.headers.authorization;
12
-
13
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
14
- return res.status(401).json({
15
- error: 'Missing or invalid Authorization header',
16
- code: 'MISSING_AUTH'
17
- });
18
- }
19
-
20
- const apiKey = authHeader.substring(7); // Remove 'Bearer '
21
- const commands = createAdminScriptCommands();
22
- const result = await commands.validateAdminApiKey(apiKey);
23
-
24
- if (result.error) {
25
- return res.status(result.error).json({
26
- error: result.reason,
27
- code: result.code
28
- });
29
- }
30
-
31
- // Attach validated key info to request for audit trail
32
- req.adminApiKey = result.apiKey;
33
- req.adminAudit = {
34
- apiKeyName: result.apiKey.name,
35
- apiKeyLast4: result.apiKey.keyLast4,
36
- ipAddress: req.ip || req.connection?.remoteAddress || 'unknown'
37
- };
38
8
 
39
- next();
40
- } catch (error) {
41
- console.error('Admin auth middleware error:', error);
42
- res.status(500).json({
43
- error: 'Authentication failed',
44
- code: 'AUTH_ERROR'
45
- });
46
- }
47
- }
9
+ const { validateAdminApiKey } = require('@friggframework/core/handlers/middleware/admin-auth');
48
10
 
49
- module.exports = { adminAuthMiddleware };
11
+ module.exports = { validateAdminApiKey };
@@ -1,6 +1,6 @@
1
1
  const express = require('express');
2
2
  const serverless = require('serverless-http');
3
- const { adminAuthMiddleware } = require('./admin-auth-middleware');
3
+ const { validateAdminApiKey } = require('./admin-auth-middleware');
4
4
  const { getScriptFactory } = require('../application/script-factory');
5
5
  const { createScriptRunner } = require('../application/script-runner');
6
6
  const { createAdminScriptCommands } = require('@friggframework/core/application/commands/admin-script-commands');
@@ -11,7 +11,7 @@ const { ScheduleManagementUseCase } = require('../application/schedule-managemen
11
11
  const router = express.Router();
12
12
 
13
13
  // Apply auth middleware to all admin routes
14
- router.use(adminAuthMiddleware);
14
+ router.use(validateAdminApiKey);
15
15
 
16
16
  /**
17
17
  * Create ScheduleManagementUseCase instance
@@ -87,10 +87,10 @@ router.get('/scripts/:scriptName', async (req, res) => {
87
87
  });
88
88
 
89
89
  /**
90
- * POST /admin/scripts/:scriptName/execute
90
+ * POST /admin/scripts/:scriptName
91
91
  * Execute a script (sync, async, or dry-run)
92
92
  */
93
- router.post('/scripts/:scriptName/execute', async (req, res) => {
93
+ router.post('/scripts/:scriptName', async (req, res) => {
94
94
  try {
95
95
  const { scriptName } = req.params;
96
96
  const { params = {}, mode = 'async', dryRun = false } = req.body;
@@ -110,7 +110,6 @@ router.post('/scripts/:scriptName/execute', async (req, res) => {
110
110
  trigger: 'MANUAL',
111
111
  mode: 'sync',
112
112
  dryRun: true,
113
- audit: req.adminAudit,
114
113
  });
115
114
  return res.json(result);
116
115
  }
@@ -121,20 +120,18 @@ router.post('/scripts/:scriptName/execute', async (req, res) => {
121
120
  const result = await runner.execute(scriptName, params, {
122
121
  trigger: 'MANUAL',
123
122
  mode: 'sync',
124
- audit: req.adminAudit,
125
123
  });
126
124
  return res.json(result);
127
125
  }
128
126
 
129
127
  // Async execution - queue and return immediately
130
128
  const commands = createAdminScriptCommands();
131
- const execution = await commands.createScriptExecution({
129
+ const execution = await commands.createAdminProcess({
132
130
  scriptName,
133
131
  scriptVersion: factory.get(scriptName).Definition.version,
134
132
  trigger: 'MANUAL',
135
133
  mode: 'async',
136
134
  input: params,
137
- audit: req.adminAudit,
138
135
  });
139
136
 
140
137
  // Queue the execution
@@ -161,14 +158,14 @@ router.post('/scripts/:scriptName/execute', async (req, res) => {
161
158
  });
162
159
 
163
160
  /**
164
- * GET /admin/executions/:executionId
165
- * Get execution status
161
+ * GET /admin/scripts/:scriptName/executions/:executionId
162
+ * Get execution status for specific script
166
163
  */
167
- router.get('/executions/:executionId', async (req, res) => {
164
+ router.get('/scripts/:scriptName/executions/:executionId', async (req, res) => {
168
165
  try {
169
166
  const { executionId } = req.params;
170
167
  const commands = createAdminScriptCommands();
171
- const execution = await commands.findScriptExecutionById(executionId);
168
+ const execution = await commands.findAdminProcessById(executionId);
172
169
 
173
170
  if (execution.error) {
174
171
  return res.status(execution.error).json({
@@ -185,12 +182,13 @@ router.get('/executions/:executionId', async (req, res) => {
185
182
  });
186
183
 
187
184
  /**
188
- * GET /admin/executions
189
- * List recent executions
185
+ * GET /admin/scripts/:scriptName/executions
186
+ * List recent executions for specific script
190
187
  */
191
- router.get('/executions', async (req, res) => {
188
+ router.get('/scripts/:scriptName/executions', async (req, res) => {
192
189
  try {
193
- const { scriptName, status, limit = 50 } = req.query;
190
+ const { scriptName } = req.params;
191
+ const { status, limit = 50 } = req.query;
194
192
  const commands = createAdminScriptCommands();
195
193
 
196
194
  const executions = await commands.findRecentExecutions({
@@ -21,7 +21,7 @@ async function handler(event) {
21
21
 
22
22
  // If executionId provided (async from API), update existing record
23
23
  if (executionId) {
24
- await commands.updateScriptExecutionStatus(executionId, 'RUNNING');
24
+ await commands.updateAdminProcessState(executionId, 'RUNNING');
25
25
  }
26
26
 
27
27
  const result = await runner.execute(scriptName, params, {
@@ -45,7 +45,7 @@ async function handler(event) {
45
45
  if (executionId) {
46
46
  const commands = createAdminScriptCommands();
47
47
  await commands
48
- .completeScriptExecution(executionId, {
48
+ .completeAdminProcess(executionId, {
49
49
  status: 'FAILED',
50
50
  error: {
51
51
  name: error.name,