import { Component, Inject, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormArray } from '@angular/forms'; import { FormsModule } from '@angular/forms'; import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; import { MatTabsModule } from '@angular/material/tabs'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; import { MatDividerModule } from '@angular/material/divider'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatChipsModule } from '@angular/material/chips'; import { MatMenuModule } from '@angular/material/menu'; import { MatTableModule } from '@angular/material/table'; import { ApiService } from '../../services/api.service'; import { JsonEditorComponent } from '../../shared/json-editor/json-editor.component'; @Component({ selector: 'app-api-edit-dialog', standalone: true, imports: [ CommonModule, ReactiveFormsModule, FormsModule, MatDialogModule, MatTabsModule, MatFormFieldModule, MatInputModule, MatSelectModule, MatCheckboxModule, MatButtonModule, MatIconModule, MatSnackBarModule, MatDividerModule, MatExpansionModule, MatChipsModule, MatMenuModule, MatTableModule, JsonEditorComponent ], templateUrl: './api-edit-dialog.component.html', styleUrls: ['./api-edit-dialog.component.scss'] }) export default class ApiEditDialogComponent implements OnInit { form!: FormGroup; saving = false; testing = false; testResult: any = null; testRequestJson = '{}'; allIntentParameters: string[] = []; responseMappingVariables: string[] = []; activeTabIndex = 0; httpMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']; retryStrategies = ['static', 'exponential']; variableTypes = ['str', 'int', 'float', 'bool', 'date']; constructor( private fb: FormBuilder, private apiService: ApiService, private snackBar: MatSnackBar, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any ) {} ngOnInit() { this.initializeForm(); this.loadIntentParameters(); // Aktif tab'ı ayarla if (this.data.activeTab !== undefined) { this.activeTabIndex = this.data.activeTab; } if ((this.data.mode === 'edit' || this.data.mode === 'test') && this.data.api) { this.populateForm(this.data.api); } else if (this.data.mode === 'duplicate' && this.data.api) { const duplicateData = { ...this.data.api }; duplicateData.name = duplicateData.name + '_copy'; delete duplicateData.last_update_date; this.populateForm(duplicateData); } // Test modunda açıldıysa test JSON'ını hazırla if (this.data.mode === 'test') { setTimeout(() => { this.updateTestRequestJson(); }, 100); } // Watch response mappings changes this.form.get('response_mappings')?.valueChanges.subscribe(() => { this.updateResponseMappingVariables(); }); } initializeForm() { this.form = this.fb.group({ // General Tab name: ['', [Validators.required, Validators.pattern(/^[a-zA-Z0-9_]+$/)]], url: ['', [Validators.required, Validators.pattern(/^https?:\/\/.+/)]], method: ['POST', Validators.required], body_template: ['{}'], timeout_seconds: [10, [Validators.required, Validators.min(1), Validators.max(300)]], response_prompt: [''], response_mappings: this.fb.array([]), // Headers Tab headers: this.fb.array([]), // Retry Settings retry: this.fb.group({ retry_count: [3, [Validators.required, Validators.min(0), Validators.max(10)]], backoff_seconds: [2, [Validators.required, Validators.min(1), Validators.max(60)]], strategy: ['static', Validators.required] }), // Auth Tab auth: this.fb.group({ enabled: [false], token_endpoint: [''], response_token_path: ['token'], token_request_body: ['{}'], token_refresh_endpoint: [''], token_refresh_body: ['{}'] }), // Proxy (optional) proxy: [''], // For race condition handling last_update_date: [''] }); // Watch for auth enabled changes this.form.get('auth.enabled')?.valueChanges.subscribe(enabled => { const authGroup = this.form.get('auth'); if (enabled) { authGroup?.get('token_endpoint')?.setValidators([Validators.required]); authGroup?.get('response_token_path')?.setValidators([Validators.required]); } else { authGroup?.get('token_endpoint')?.clearValidators(); authGroup?.get('response_token_path')?.clearValidators(); } authGroup?.get('token_endpoint')?.updateValueAndValidity(); authGroup?.get('response_token_path')?.updateValueAndValidity(); }); } populateForm(api: any) { console.log('Populating form with API:', api); // Convert headers object to FormArray const headersArray = this.form.get('headers') as FormArray; headersArray.clear(); if (api.headers) { if (Array.isArray(api.headers)) { api.headers.forEach((header: any) => { headersArray.push(this.createHeaderFormGroup(header.key || '', header.value || '')); }); } else if (typeof api.headers === 'object') { Object.entries(api.headers).forEach(([key, value]) => { headersArray.push(this.createHeaderFormGroup(key, value as string)); }); } } // Convert response_mappings to FormArray const responseMappingsArray = this.form.get('response_mappings') as FormArray; responseMappingsArray.clear(); if (api.response_mappings && Array.isArray(api.response_mappings)) { api.response_mappings.forEach((mapping: any) => { responseMappingsArray.push(this.createResponseMappingFormGroup(mapping)); }); } // Convert body_template to JSON string if it's an object if (api.body_template && typeof api.body_template === 'object') { api.body_template = JSON.stringify(api.body_template, null, 2); } // Convert auth bodies to JSON strings if (api.auth) { if (api.auth.token_request_body && typeof api.auth.token_request_body === 'object') { api.auth.token_request_body = JSON.stringify(api.auth.token_request_body, null, 2); } if (api.auth.token_refresh_body && typeof api.auth.token_refresh_body === 'object') { api.auth.token_refresh_body = JSON.stringify(api.auth.token_refresh_body, null, 2); } } const formData = { ...api }; // headers array'ini kaldır çünkü zaten FormArray'e ekledik delete formData.headers; delete formData.response_mappings; // Patch form values this.form.patchValue(formData); // Disable name field if editing or testing if (this.data.mode === 'edit' || this.data.mode === 'test') { this.form.get('name')?.disable(); } } get headers() { return this.form.get('headers') as FormArray; } get responseMappings() { return this.form.get('response_mappings') as FormArray; } createHeaderFormGroup(key = '', value = ''): FormGroup { return this.fb.group({ key: [key, Validators.required], value: [value, Validators.required] }); } createResponseMappingFormGroup(data: any = {}): FormGroup { return this.fb.group({ variable_name: [data.variable_name || '', [Validators.required, Validators.pattern(/^[a-z_][a-z0-9_]*$/)]], type: [data.type || 'str', Validators.required], json_path: [data.json_path || '', Validators.required], caption: [data.caption || '', Validators.required] }); } addHeader() { this.headers.push(this.createHeaderFormGroup()); } removeHeader(index: number) { this.headers.removeAt(index); } addResponseMapping() { this.responseMappings.push(this.createResponseMappingFormGroup()); } removeResponseMapping(index: number) { this.responseMappings.removeAt(index); } insertHeaderValue(index: number, variable: string) { const headerGroup = this.headers.at(index); if (headerGroup) { const valueControl = headerGroup.get('value'); if (valueControl) { const currentValue = valueControl.value || ''; const newValue = currentValue + `{{${variable}}}`; valueControl.setValue(newValue); } } } getTemplateVariables(includeResponseMappings = true): string[] { const variables = new Set(); // Intent parameters this.allIntentParameters.forEach(param => { variables.add(`variables.${param}`); }); // Auth tokens const apiName = this.form.get('name')?.value || 'api_name'; variables.add(`auth_tokens.${apiName}.token`); // Response mappings if (includeResponseMappings) { this.responseMappingVariables.forEach(varName => { variables.add(`variables.${varName}`); }); } // Config variables variables.add('config.work_mode'); variables.add('config.cloud_token'); return Array.from(variables).sort(); } updateResponseMappingVariables() { this.responseMappingVariables = []; const mappings = this.responseMappings.value; mappings.forEach((mapping: any) => { if (mapping.variable_name) { this.responseMappingVariables.push(mapping.variable_name); } }); } async loadIntentParameters() { try { const projects = await this.apiService.getProjects(false).toPromise(); const params = new Set(); projects?.forEach(project => { project.versions?.forEach(version => { version.intents?.forEach(intent => { intent.parameters?.forEach((param: any) => { if (param.variable_name) { params.add(param.variable_name); } }); }); }); }); this.allIntentParameters = Array.from(params).sort(); } catch (error) { console.error('Failed to load intent parameters:', error); } } // JSON validation için replacer fonksiyonu replaceVariablesForValidation = (jsonStr: string): string => { let processed = jsonStr; processed = processed.replace(/\{\{([^}]+)\}\}/g, (match, variablePath) => { if (variablePath.includes('variables.')) { const varName = variablePath.split('.').pop()?.toLowerCase() || ''; const numericVars = ['count', 'passenger_count', 'timeout_seconds', 'retry_count', 'amount', 'price', 'quantity', 'age', 'id']; const booleanVars = ['enabled', 'published', 'is_active', 'confirmed', 'canceled', 'deleted', 'required']; if (numericVars.some(v => varName.includes(v))) { return '1'; } else if (booleanVars.some(v => varName.includes(v))) { return 'true'; } else { return '"placeholder"'; } } return '"placeholder"'; }); return processed; } async testAPI() { const generalValid = this.form.get('url')?.valid && this.form.get('method')?.valid; if (!generalValid) { this.snackBar.open('Please fill in required fields first', 'Close', { duration: 3000 }); return; } this.testing = true; this.testResult = null; try { const testData = this.prepareAPIData(); let testRequestData = {}; try { testRequestData = JSON.parse(this.testRequestJson); } catch (e) { this.snackBar.open('Invalid test request JSON', 'Close', { duration: 3000, panelClass: 'error-snackbar' }); this.testing = false; return; } testData.test_request = testRequestData; const result = await this.apiService.testAPI(testData).toPromise(); // Response headers'ı obje olarak sakla if (result.response_headers && typeof result.response_headers === 'string') { try { result.response_headers = JSON.parse(result.response_headers); } catch { // Headers parse edilemezse string olarak bırak } } this.testResult = result; if (result.success) { this.snackBar.open(`API test successful! (${result.status_code})`, 'Close', { duration: 3000 }); } else { const errorMsg = result.error || `API returned status ${result.status_code}`; this.snackBar.open(`API test failed: ${errorMsg}`, 'Close', { duration: 5000, panelClass: 'error-snackbar' }); } } catch (error: any) { this.testResult = { success: false, error: error.message || 'Test failed' }; this.snackBar.open('API test failed', 'Close', { duration: 3000, panelClass: 'error-snackbar' }); } finally { this.testing = false; } } updateTestRequestJson() { const formValue = this.form.getRawValue(); let bodyTemplate = {}; try { bodyTemplate = JSON.parse(formValue.body_template); } catch { bodyTemplate = {}; } const testData = this.replacePlaceholdersForTest(bodyTemplate); this.testRequestJson = JSON.stringify(testData, null, 2); } replacePlaceholdersForTest(obj: any): any { if (typeof obj === 'string') { let result = obj; result = result.replace(/\{\{variables\.origin\}\}/g, 'Istanbul'); result = result.replace(/\{\{variables\.destination\}\}/g, 'Ankara'); result = result.replace(/\{\{variables\.flight_date\}\}/g, '2025-06-15'); result = result.replace(/\{\{variables\.passenger_count\}\}/g, '2'); result = result.replace(/\{\{variables\.flight_number\}\}/g, 'TK123'); result = result.replace(/\{\{variables\.pnr\}\}/g, 'ABC12'); result = result.replace(/\{\{variables\.surname\}\}/g, 'Test'); result = result.replace(/\{\{auth_tokens\.[^}]+\.token\}\}/g, 'test_token_123'); result = result.replace(/\{\{config\.work_mode\}\}/g, 'hfcloud'); result = result.replace(/\{\{[^}]+\}\}/g, 'test_value'); return result; } else if (typeof obj === 'object' && obj !== null) { const result: any = Array.isArray(obj) ? [] : {}; for (const key in obj) { result[key] = this.replacePlaceholdersForTest(obj[key]); } return result; } return obj; } prepareAPIData(): any { const formValue = this.form.getRawValue(); const headers: any = {}; formValue.headers.forEach((h: any) => { if (h.key && h.value) { headers[h.key] = h.value; } }); let body_template = {}; let auth_token_request_body = {}; let auth_token_refresh_body = {}; try { body_template = formValue.body_template ? JSON.parse(formValue.body_template) : {}; } catch (e) { console.error('Invalid body_template JSON:', e); } try { auth_token_request_body = formValue.auth.token_request_body ? JSON.parse(formValue.auth.token_request_body) : {}; } catch (e) { console.error('Invalid auth token_request_body JSON:', e); } try { auth_token_refresh_body = formValue.auth.token_refresh_body ? JSON.parse(formValue.auth.token_refresh_body) : {}; } catch (e) { console.error('Invalid auth token_refresh_body JSON:', e); } const apiData: any = { name: formValue.name, url: formValue.url, method: formValue.method, headers, body_template, timeout_seconds: formValue.timeout_seconds, retry: formValue.retry, response_prompt: formValue.response_prompt, response_mappings: formValue.response_mappings || [] }; // Proxy - null olarak gönder boşsa apiData.proxy = formValue.proxy || null; if (formValue.proxy) { apiData.proxy = formValue.proxy; } if (formValue.auth.enabled) { apiData.auth = { enabled: true, token_endpoint: formValue.auth.token_endpoint, response_token_path: formValue.auth.response_token_path, token_request_body: auth_token_request_body }; if (formValue.auth.token_refresh_endpoint) { apiData.auth.token_refresh_endpoint = formValue.auth.token_refresh_endpoint; apiData.auth.token_refresh_body = auth_token_refresh_body; } }else { // Auth disabled olsa bile null olarak gönder apiData.auth = null; } // Edit modunda last_update_date'i ekle if (this.data.mode === 'edit' && formValue.last_update_date) { apiData.last_update_date = formValue.last_update_date; } console.log('Prepared API data:', apiData); return apiData; } async save() { if (this.data.mode === 'test') { this.cancel(); return; } if (this.form.invalid) { Object.keys(this.form.controls).forEach(key => { this.form.get(key)?.markAsTouched(); }); this.snackBar.open('Please fix validation errors', 'Close', { duration: 3000 }); return; } this.saving = true; try { const apiData = this.prepareAPIData(); if (this.data.mode === 'create' || this.data.mode === 'duplicate') { await this.apiService.createAPI(apiData).toPromise(); this.snackBar.open('API created successfully', 'Close', { duration: 3000 }); } else { await this.apiService.updateAPI(this.data.api.name, apiData).toPromise(); this.snackBar.open('API updated successfully', 'Close', { duration: 3000 }); } this.dialogRef.close(true); } catch (error: any) { const message = error.error?.detail || (this.data.mode === 'create' ? 'Failed to create API' : 'Failed to update API'); this.snackBar.open(message, 'Close', { duration: 5000, panelClass: 'error-snackbar' }); } finally { this.saving = false; } } cancel() { this.dialogRef.close(false); } }