import { Component, Inject, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormArray } 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 { ApiService } from '../../services/api.service'; @Component({ selector: 'app-api-edit-dialog', standalone: true, imports: [ CommonModule, ReactiveFormsModule, MatDialogModule, MatTabsModule, MatFormFieldModule, MatInputModule, MatSelectModule, MatCheckboxModule, MatButtonModule, MatIconModule, MatSnackBarModule, MatDividerModule, MatExpansionModule, MatChipsModule ], 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[] = []; 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(); if (this.data.mode === 'edit' && 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); } // 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) { // Convert headers object to FormArray const headersArray = this.form.get('headers') as FormArray; headersArray.clear(); if (api.headers && 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); } } // Patch form values this.form.patchValue(api); // Disable name field if editing if (this.data.mode === 'edit') { 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] }); } 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); } insertTemplateVariable(field: string, variable: string) { const control = field.includes('.') ? this.form.get(field) : this.form.get(field); if (control) { const currentValue = control.value || ''; const newValue = currentValue + `{{${variable}}}`; control.setValue(newValue); } } 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 { // Tüm projeleri al 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); } } validateJSON(field: string): boolean { const control = this.form.get(field); if (!control || !control.value) return true; try { const jsonStr = control.value; // Template değişkenlerini placeholder değerlerle değiştir const processedJson = this.replaceVariablesForValidation(jsonStr); JSON.parse(processedJson); return true; } catch { return false; } } replaceVariablesForValidation(jsonStr: string): string { let processed = jsonStr; // Numeric değişkenler için const numericVars = ['passenger_count', 'count', 'timeout_seconds', 'retry_count']; numericVars.forEach(varName => { const regex = new RegExp(`{{\\s*variables\\.${varName}\\s*}}`, 'g'); processed = processed.replace(regex, '1'); }); // Boolean değişkenler için const booleanVars = ['enabled', 'published']; booleanVars.forEach(varName => { const regex = new RegExp(`{{\\s*variables\\.${varName}\\s*}}`, 'g'); processed = processed.replace(regex, 'true'); }); // Diğer tüm değişkenler için string değer processed = processed.replace(/\{\{[^}]+\}\}/g, '"placeholder"'); return processed; } async testAPI() { // Validate form first 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(); // Test için request JSON'ını hazırla const testRequestData = this.prepareTestRequest(); testData.test_request = testRequestData; const result = await this.apiService.testAPI(testData).toPromise(); this.testResult = result; if (result.success) { this.snackBar.open('API test successful!', 'Close', { duration: 3000 }); } else { this.snackBar.open(`API test failed: ${result.error}`, '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; } } prepareTestRequest(): any { try { const requestData = JSON.parse(this.testRequestJson); return requestData; } catch { return {}; } } updateTestRequestJson() { const formValue = this.form.getRawValue(); let bodyTemplate = {}; try { bodyTemplate = JSON.parse(formValue.body_template); } catch { bodyTemplate = {}; } // Placeholder değerlerle doldur const testData = this.replacePlaceholdersForTest(bodyTemplate); this.testRequestJson = JSON.stringify(testData, null, 2); } replacePlaceholdersForTest(obj: any): any { if (typeof obj === 'string') { // Template değişkenlerini test değerleriyle değiştir let result = obj; // Intent parameters 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'); // Auth tokens result = result.replace(/\{\{auth_tokens\.[^}]+\.token\}\}/g, 'test_token_123'); // Config result = result.replace(/\{\{config\.work_mode\}\}/g, 'hfcloud'); // Diğer değişkenler 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(); // Convert headers array back to object const headers: any = {}; formValue.headers.forEach((h: any) => { if (h.key && h.value) { headers[h.key] = h.value; } }); // Parse JSON fields 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); } // Prepare final data 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 || [] }; // Add proxy if specified if (formValue.proxy) { apiData.proxy = formValue.proxy; } // Add auth if enabled 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; } } // Add last_update_date for edit mode if (this.data.mode === 'edit' && formValue.last_update_date) { apiData.last_update_date = formValue.last_update_date; } return apiData; } async save() { if (this.form.invalid) { // Mark all fields as touched to show validation errors Object.keys(this.form.controls).forEach(key => { this.form.get(key)?.markAsTouched(); }); // Check specific JSON fields const jsonFields = ['body_template', 'auth.token_request_body', 'auth.token_refresh_body']; for (const field of jsonFields) { if (!this.validateJSON(field)) { this.snackBar.open(`Invalid JSON in ${field}`, 'Close', { duration: 3000, panelClass: 'error-snackbar' }); return; } } 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); } }