From ed9b862b82723ee11d7d7302e06abb52a8982414 Mon Sep 17 00:00:00 2001 From: Zahirul Iman Date: Sun, 1 Jun 2025 18:40:19 +0800 Subject: [PATCH] Add API Platform functionality with authentication and management features --- .cursorignore | 1 + PHASE-3-IMPLEMENTATION-SUMMARY.md | 191 +++++ .../api-platform/CodeGenerationModal.vue | 634 +++++++++++++++++ .../api-platform/CollectionsSidebar.vue | 318 +++++++++ .../api-platform/CreateCollectionModal.vue | 87 +++ components/api-platform/EnvironmentModal.vue | 226 ++++++ .../api-platform/EnvironmentSelector.vue | 61 ++ components/api-platform/ImportExportModal.vue | 669 ++++++++++++++++++ components/api-platform/RequestBuilder.vue | 247 +++++++ components/api-platform/ResponseViewer.vue | 586 +++++++++++++++ components/api-platform/SaveRequestModal.vue | 149 ++++ components/api-platform/tabs/AuthTab.vue | 72 ++ components/api-platform/tabs/BodyTab.vue | 436 ++++++++++++ components/api-platform/tabs/HeadersTab.vue | 51 ++ components/api-platform/tabs/ParamsTab.vue | 51 ++ composables/useApiPlatform.js | 136 ++++ composables/useApiRequest.js | 136 ++++ composables/useVariableSubstitution.js | 159 +++++ ...rrent Implementation Analysis (first g.ini | 79 +++ navigation/index.js | 6 + package.json | 4 +- pages/api-platform/index.vue | 188 +++++ pages/oauth/callback.vue | 88 +++ prisma/schema.prisma | 232 ++++++ server/api/api-platform/send-request.post.js | 267 +++++++ yarn.lock | 73 +- 26 files changed, 5127 insertions(+), 20 deletions(-) create mode 100644 .cursorignore create mode 100644 PHASE-3-IMPLEMENTATION-SUMMARY.md create mode 100644 components/api-platform/CodeGenerationModal.vue create mode 100644 components/api-platform/CollectionsSidebar.vue create mode 100644 components/api-platform/CreateCollectionModal.vue create mode 100644 components/api-platform/EnvironmentModal.vue create mode 100644 components/api-platform/EnvironmentSelector.vue create mode 100644 components/api-platform/ImportExportModal.vue create mode 100644 components/api-platform/RequestBuilder.vue create mode 100644 components/api-platform/ResponseViewer.vue create mode 100644 components/api-platform/SaveRequestModal.vue create mode 100644 components/api-platform/tabs/AuthTab.vue create mode 100644 components/api-platform/tabs/BodyTab.vue create mode 100644 components/api-platform/tabs/HeadersTab.vue create mode 100644 components/api-platform/tabs/ParamsTab.vue create mode 100644 composables/useApiPlatform.js create mode 100644 composables/useApiRequest.js create mode 100644 composables/useVariableSubstitution.js create mode 100644 docs/Current Implementation Analysis (first g.ini create mode 100644 pages/api-platform/index.vue create mode 100644 pages/oauth/callback.vue create mode 100644 server/api/api-platform/send-request.post.js diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000..6f9f00f --- /dev/null +++ b/.cursorignore @@ -0,0 +1 @@ +# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) diff --git a/PHASE-3-IMPLEMENTATION-SUMMARY.md b/PHASE-3-IMPLEMENTATION-SUMMARY.md new file mode 100644 index 0000000..35f92c5 --- /dev/null +++ b/PHASE-3-IMPLEMENTATION-SUMMARY.md @@ -0,0 +1,191 @@ +# Phase 3: Collections & Environment Management - Implementation Summary + +## ✅ Completed Features + +### 1. Collections Sidebar with Tree View +- **File**: `components/api-platform/CollectionsSidebar.vue` +- **Features**: + - Collapsible collections with tree structure + - Request management within collections + - Quick actions: Edit, Delete, Add Request + - Recent requests section (last 5) + - Responsive design with mobile support + +### 2. Collection Management +- **File**: `components/api-platform/CreateCollectionModal.vue` +- **Features**: + - Create new collections with name and description + - Modal-based interface + - Form validation + +### 3. Save/Load Request Functionality +- **File**: `components/api-platform/SaveRequestModal.vue` +- **Features**: + - Save current request to any collection + - Auto-populate request name + - Create collections on-the-fly + - Complete request data preservation (params, headers, auth, body) + +### 4. Environment Management +- **File**: `components/api-platform/EnvironmentSelector.vue` +- **File**: `components/api-platform/EnvironmentModal.vue` +- **Features**: + - Environment dropdown selector + - Full environment CRUD operations + - Variable management per environment + - Visual indicators for variable count + +### 5. Variable Substitution System +- **File**: `composables/useVariableSubstitution.js` +- **Features**: + - Template syntax: `{{variableName}}` + - Complete request processing (URL, headers, params, auth, body) + - Real-time variable detection and validation + - Visual indicators in UI + +### 6. Persistence Layer +- **Storage**: localStorage +- **Features**: + - Automatic saving of collections and environments + - Persistent data across browser sessions + - JSON-based storage format + +## 🎯 User Interface Enhancements + +### Main Layout Updates +- **File**: `pages/api-platform/index.vue` +- Added collections sidebar toggle +- Environment selector in top bar +- Save request button +- Responsive layout adjustments + +### Request Builder Enhancements +- **File**: `components/api-platform/RequestBuilder.vue` +- Variable detection in URL field +- Variable preview panel +- Current environment indicator +- Integrated variable substitution + +### Global State Management +- **File**: `composables/useApiPlatform.js` +- Added UI state management +- Collections and environments state +- Modal management + +## 🔧 Technical Implementation + +### Variable Substitution Flow +1. User enters `{{variableName}}` in any field +2. System detects variables and shows indicators +3. On request send, variables are substituted with environment values +4. Visual feedback shows which variables are resolved + +### Data Structure + +#### Collections +```javascript +{ + id: timestamp, + name: "Collection Name", + description: "Optional description", + requests: [ + { + id: timestamp, + name: "Request Name", + method: "GET", + url: "{{baseUrl}}/api/endpoint", + params: [...], + headers: [...], + auth: {...}, + body: {...}, + createdAt: "ISO string" + } + ], + createdAt: "ISO string" +} +``` + +#### Environments +```javascript +{ + id: timestamp, + name: "Environment Name", + variables: [ + { + key: "baseUrl", + value: "https://api.example.com" + } + ] +} +``` + +## 🚀 Usage Examples + +### 1. Creating an Environment +1. Click environment settings icon +2. Create new environment (e.g., "Production") +3. Add variables: + - `baseUrl`: `https://api.production.com` + - `apiKey`: `prod-key-123` + +### 2. Using Variables in Requests +- URL: `{{baseUrl}}/users` +- Headers: `Authorization: Bearer {{apiKey}}` +- System automatically substitutes values when sending + +### 3. Saving Requests to Collections +1. Configure your request +2. Click "Save" button +3. Choose or create collection +4. Request is saved with all current settings + +### 4. Loading Saved Requests +1. Open collections sidebar +2. Browse collections +3. Click on any saved request +4. Request loads with all original settings + +## 📁 File Structure +``` +components/api-platform/ +├── CollectionsSidebar.vue # Main collections interface +├── CreateCollectionModal.vue # Collection creation +├── EnvironmentSelector.vue # Environment dropdown +├── EnvironmentModal.vue # Environment management +├── SaveRequestModal.vue # Request saving +└── RequestBuilder.vue # Enhanced with variables + +composables/ +├── useApiPlatform.js # Global state management +└── useVariableSubstitution.js # Variable processing + +pages/api-platform/ +└── index.vue # Main layout with sidebar +``` + +## ✨ Key Benefits + +1. **Organized Workflow**: Collections help organize related requests +2. **Environment Management**: Easy switching between dev/staging/prod +3. **Variable Substitution**: DRY principle for common values +4. **Persistent Storage**: Work survives browser restarts +5. **Team Collaboration**: Exportable collections (future feature) +6. **Professional UX**: Modern interface with visual feedback + +## 🔄 Integration with Existing Features + +- ✅ Fully compatible with existing request/response system +- ✅ Works with all authentication methods +- ✅ Integrates with notification system +- ✅ Maintains all existing functionality +- ✅ No breaking changes to existing components + +## 🎯 Phase 3 Goals - Status + +- ✅ Collections sidebar with tree view +- ✅ Save/Load requests to collections +- ✅ Environment selector dropdown +- ✅ Variable substitution ({{baseUrl}}) +- ✅ Persistence layer (localStorage) + +**Phase 3 is now complete and ready for use!** \ No newline at end of file diff --git a/components/api-platform/CodeGenerationModal.vue b/components/api-platform/CodeGenerationModal.vue new file mode 100644 index 0000000..9956d70 --- /dev/null +++ b/components/api-platform/CodeGenerationModal.vue @@ -0,0 +1,634 @@ + + + \ No newline at end of file diff --git a/components/api-platform/CollectionsSidebar.vue b/components/api-platform/CollectionsSidebar.vue new file mode 100644 index 0000000..125aac2 --- /dev/null +++ b/components/api-platform/CollectionsSidebar.vue @@ -0,0 +1,318 @@ + + + \ No newline at end of file diff --git a/components/api-platform/CreateCollectionModal.vue b/components/api-platform/CreateCollectionModal.vue new file mode 100644 index 0000000..2f2c11a --- /dev/null +++ b/components/api-platform/CreateCollectionModal.vue @@ -0,0 +1,87 @@ + + + \ No newline at end of file diff --git a/components/api-platform/EnvironmentModal.vue b/components/api-platform/EnvironmentModal.vue new file mode 100644 index 0000000..35c5170 --- /dev/null +++ b/components/api-platform/EnvironmentModal.vue @@ -0,0 +1,226 @@ + + + \ No newline at end of file diff --git a/components/api-platform/EnvironmentSelector.vue b/components/api-platform/EnvironmentSelector.vue new file mode 100644 index 0000000..f1d742b --- /dev/null +++ b/components/api-platform/EnvironmentSelector.vue @@ -0,0 +1,61 @@ + + + \ No newline at end of file diff --git a/components/api-platform/ImportExportModal.vue b/components/api-platform/ImportExportModal.vue new file mode 100644 index 0000000..391e751 --- /dev/null +++ b/components/api-platform/ImportExportModal.vue @@ -0,0 +1,669 @@ + + + \ No newline at end of file diff --git a/components/api-platform/RequestBuilder.vue b/components/api-platform/RequestBuilder.vue new file mode 100644 index 0000000..5f5737a --- /dev/null +++ b/components/api-platform/RequestBuilder.vue @@ -0,0 +1,247 @@ + + + \ No newline at end of file diff --git a/components/api-platform/ResponseViewer.vue b/components/api-platform/ResponseViewer.vue new file mode 100644 index 0000000..1c1a3ad --- /dev/null +++ b/components/api-platform/ResponseViewer.vue @@ -0,0 +1,586 @@ + + + \ No newline at end of file diff --git a/components/api-platform/SaveRequestModal.vue b/components/api-platform/SaveRequestModal.vue new file mode 100644 index 0000000..b607d61 --- /dev/null +++ b/components/api-platform/SaveRequestModal.vue @@ -0,0 +1,149 @@ + + + \ No newline at end of file diff --git a/components/api-platform/tabs/AuthTab.vue b/components/api-platform/tabs/AuthTab.vue new file mode 100644 index 0000000..31d7526 --- /dev/null +++ b/components/api-platform/tabs/AuthTab.vue @@ -0,0 +1,72 @@ + + + \ No newline at end of file diff --git a/components/api-platform/tabs/BodyTab.vue b/components/api-platform/tabs/BodyTab.vue new file mode 100644 index 0000000..1049ae8 --- /dev/null +++ b/components/api-platform/tabs/BodyTab.vue @@ -0,0 +1,436 @@ + + + \ No newline at end of file diff --git a/components/api-platform/tabs/HeadersTab.vue b/components/api-platform/tabs/HeadersTab.vue new file mode 100644 index 0000000..010ad0c --- /dev/null +++ b/components/api-platform/tabs/HeadersTab.vue @@ -0,0 +1,51 @@ + + + \ No newline at end of file diff --git a/components/api-platform/tabs/ParamsTab.vue b/components/api-platform/tabs/ParamsTab.vue new file mode 100644 index 0000000..43611dc --- /dev/null +++ b/components/api-platform/tabs/ParamsTab.vue @@ -0,0 +1,51 @@ + + + \ No newline at end of file diff --git a/composables/useApiPlatform.js b/composables/useApiPlatform.js new file mode 100644 index 0000000..ab89e21 --- /dev/null +++ b/composables/useApiPlatform.js @@ -0,0 +1,136 @@ +// Global reactive state +const globalState = { + // Core reactive state + activeTab: ref('params'), + httpMethod: ref('GET'), + requestUrl: ref(''), + isLoading: ref(false), + requestName: ref(''), + + // UI State + showCollectionSidebar: ref(true), + showSaveRequestModal: ref(false), + selectedEnvironment: ref(''), + + // Notification system + notifications: ref([]), + + // Request data + requestParams: ref([ + { active: true, key: '', value: '', description: '' } + ]), + requestHeaders: ref([ + { active: true, key: '', value: '', description: '' } + ]), + requestAuth: ref({ + type: 'none', + bearer: '', + basic: { username: '', password: '' }, + apiKey: { key: '', value: '', addTo: 'header' }, + oauth2: { + grantType: 'authorization_code', + authUrl: '', + accessTokenUrl: '', + clientId: '', + clientSecret: '', + scope: '', + redirectUri: '', + state: '', + accessToken: '', + refreshToken: '', + tokenType: 'Bearer', + expiresIn: null, + expiresAt: null + } + }), + requestBody: ref({ + type: 'raw', + raw: '', + formData: [{ active: true, key: '', value: '', description: '', type: 'text', file: null }], + urlEncoded: [{ active: true, key: '', value: '', description: '' }] + }), + + // Response data + response: ref({ + status: null, + statusText: '', + headers: {}, + data: null, + time: 0, + size: 0 + }), + responseActiveTab: ref('body'), + + // Collections Management (simplified for development) + collections: ref([]), + + // Environment Management (simplified for development) + environments: ref([]), + + // History Management (start empty) + requestHistory: ref([]) +} + +const httpMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] + +// Global methods +const showNotification = (message, type = 'info', duration = 3000) => { + const id = Date.now() + const notification = { id, message, type } + globalState.notifications.value.push(notification) + + setTimeout(() => { + const index = globalState.notifications.value.findIndex(n => n.id === id) + if (index > -1) { + globalState.notifications.value.splice(index, 1) + } + }, duration) +} + +const dismissNotification = (id) => { + const index = globalState.notifications.value.findIndex(n => n.id === id) + if (index > -1) { + globalState.notifications.value.splice(index, 1) + } +} + +export const useApiPlatform = () => { + return { + // Core state + activeTab: globalState.activeTab, + httpMethod: globalState.httpMethod, + requestUrl: globalState.requestUrl, + isLoading: globalState.isLoading, + requestName: globalState.requestName, + + // UI State + showCollectionSidebar: globalState.showCollectionSidebar, + showSaveRequestModal: globalState.showSaveRequestModal, + selectedEnvironment: globalState.selectedEnvironment, + + // Request data + requestParams: globalState.requestParams, + requestHeaders: globalState.requestHeaders, + requestAuth: globalState.requestAuth, + requestBody: globalState.requestBody, + + // Response data + response: globalState.response, + responseActiveTab: globalState.responseActiveTab, + + // History and collections + requestHistory: globalState.requestHistory, + collections: globalState.collections, + environments: globalState.environments, + + // Notifications + notifications: globalState.notifications, + + // Static data + httpMethods, + + // Methods + showNotification, + dismissNotification + } +} \ No newline at end of file diff --git a/composables/useApiRequest.js b/composables/useApiRequest.js new file mode 100644 index 0000000..628bc6e --- /dev/null +++ b/composables/useApiRequest.js @@ -0,0 +1,136 @@ +export const useApiRequest = () => { + // MAIN SEND REQUEST FUNCTION - REAL BACKEND INTEGRATION + const sendRequest = async (requestData, callbacks = {}) => { + const { onStart, onSuccess, onError, onComplete } = callbacks + + if (!requestData.url) { + onError?.('Please enter a URL') + return + } + + onStart?.() + + try { + // Prepare request data for backend + const payload = { + url: requestData.url, + method: requestData.method, + headers: requestData.headers, + params: requestData.params, + auth: requestData.auth, + requestBody: requestData.body, + timeout: 30000 + } + + // Make request to our backend proxy + const result = await $fetch('/api/api-platform/send-request', { + method: 'POST', + body: payload + }) + + if (result.statusCode === 200) { + onSuccess?.(result.data) + return result.data + } else { + onError?.(result.message, result.data || null) + return result.data + } + + } catch (error) { + const errorResponse = { + status: 500, + statusText: 'Internal Server Error', + headers: {}, + data: { error: error.message || 'Something went wrong' }, + time: 0, + size: 0 + } + + onError?.('Failed to send request', errorResponse) + return errorResponse + } finally { + onComplete?.() + } + } + + // Utility methods + const addRow = (arrayRef) => { + arrayRef.value.push({ active: true, key: '', value: '', description: '' }) + } + + const removeRow = (arrayRef, index) => { + if (arrayRef.value.length > 1) { + arrayRef.value.splice(index, 1) + } + } + + const formatJson = (obj) => { + return JSON.stringify(obj, null, 2) + } + + const getStatusVariant = (status) => { + if (status >= 200 && status < 300) return 'success' + if (status >= 300 && status < 400) return 'warning' + if (status >= 400) return 'danger' + return 'secondary' + } + + const getMethodVariant = (method) => { + const variants = { + 'GET': 'info', + 'POST': 'success', + 'PUT': 'warning', + 'PATCH': 'secondary', + 'DELETE': 'danger' + } + return variants[method] || 'primary' + } + + // Enhanced JSON utilities with notification integration + const beautifyJson = (content, { showNotification } = {}) => { + try { + const parsed = JSON.parse(content) + const beautified = JSON.stringify(parsed, null, 2) + showNotification?.('JSON beautified successfully', 'success', 2000) + return beautified + } catch (error) { + showNotification?.('Invalid JSON format', 'error') + throw error + } + } + + const minifyJson = (content, { showNotification } = {}) => { + try { + const parsed = JSON.parse(content) + const minified = JSON.stringify(parsed) + showNotification?.('JSON minified successfully', 'success', 2000) + return minified + } catch (error) { + showNotification?.('Invalid JSON format', 'error') + throw error + } + } + + const copyToClipboard = async (content, message = 'Copied to clipboard', { showNotification } = {}) => { + try { + await navigator.clipboard.writeText(content) + showNotification?.(message, 'success', 2000) + return true + } catch (error) { + showNotification?.('Failed to copy to clipboard', 'error') + return false + } + } + + return { + sendRequest, + addRow, + removeRow, + formatJson, + getStatusVariant, + getMethodVariant, + beautifyJson, + minifyJson, + copyToClipboard + } +} \ No newline at end of file diff --git a/composables/useVariableSubstitution.js b/composables/useVariableSubstitution.js new file mode 100644 index 0000000..473cc60 --- /dev/null +++ b/composables/useVariableSubstitution.js @@ -0,0 +1,159 @@ +export const useVariableSubstitution = () => { + const { environments, selectedEnvironment } = useApiPlatform() + + // Get the currently selected environment + const currentEnvironment = computed(() => { + if (!selectedEnvironment.value) return null + return environments.value.find(env => env.id === selectedEnvironment.value) + }) + + // Create a variables map for quick lookup + const variablesMap = computed(() => { + if (!currentEnvironment.value) return {} + + const map = {} + currentEnvironment.value.variables.forEach(variable => { + if (variable.key && variable.value) { + map[variable.key] = variable.value + } + }) + return map + }) + + // Replace variables in a string ({{variableName}}) + const substituteVariables = (text) => { + if (!text || typeof text !== 'string') return text + if (!currentEnvironment.value) return text + + return text.replace(/\{\{([^}]+)\}\}/g, (match, variableName) => { + const trimmedName = variableName.trim() + return variablesMap.value[trimmedName] || match + }) + } + + // Process an entire request object and substitute all variables + const processRequest = (requestData) => { + if (!currentEnvironment.value) return requestData + + const processed = JSON.parse(JSON.stringify(requestData)) + + // Substitute URL + processed.url = substituteVariables(processed.url) + + // Substitute headers + if (processed.headers && Array.isArray(processed.headers)) { + processed.headers.forEach(header => { + header.key = substituteVariables(header.key) + header.value = substituteVariables(header.value) + }) + } + + // Substitute params + if (processed.params && Array.isArray(processed.params)) { + processed.params.forEach(param => { + param.key = substituteVariables(param.key) + param.value = substituteVariables(param.value) + }) + } + + // Substitute auth values + if (processed.auth) { + if (processed.auth.bearer) { + processed.auth.bearer = substituteVariables(processed.auth.bearer) + } + if (processed.auth.basic) { + processed.auth.basic.username = substituteVariables(processed.auth.basic.username) + processed.auth.basic.password = substituteVariables(processed.auth.basic.password) + } + if (processed.auth.apiKey) { + processed.auth.apiKey.key = substituteVariables(processed.auth.apiKey.key) + processed.auth.apiKey.value = substituteVariables(processed.auth.apiKey.value) + } + if (processed.auth.oauth2) { + processed.auth.oauth2.authUrl = substituteVariables(processed.auth.oauth2.authUrl) + processed.auth.oauth2.accessTokenUrl = substituteVariables(processed.auth.oauth2.accessTokenUrl) + processed.auth.oauth2.clientId = substituteVariables(processed.auth.oauth2.clientId) + processed.auth.oauth2.clientSecret = substituteVariables(processed.auth.oauth2.clientSecret) + processed.auth.oauth2.redirectUri = substituteVariables(processed.auth.oauth2.redirectUri) + } + } + + // Substitute body content + if (processed.requestBody) { + if (processed.requestBody.raw) { + processed.requestBody.raw = substituteVariables(processed.requestBody.raw) + } + if (processed.requestBody.formData && Array.isArray(processed.requestBody.formData)) { + processed.requestBody.formData.forEach(item => { + item.key = substituteVariables(item.key) + item.value = substituteVariables(item.value) + }) + } + if (processed.requestBody.urlEncoded && Array.isArray(processed.requestBody.urlEncoded)) { + processed.requestBody.urlEncoded.forEach(item => { + item.key = substituteVariables(item.key) + item.value = substituteVariables(item.value) + }) + } + } + + return processed + } + + // Find all variables used in a text string + const findVariables = (text) => { + if (!text || typeof text !== 'string') return [] + + const matches = text.match(/\{\{([^}]+)\}\}/g) + if (!matches) return [] + + return matches.map(match => { + const variableName = match.replace(/[{}]/g, '').trim() + return { + name: variableName, + found: !!variablesMap.value[variableName], + value: variablesMap.value[variableName] || null + } + }) + } + + // Get all variables used in the current request + const getRequestVariables = (requestData) => { + const variables = new Set() + + // Check URL + findVariables(requestData.url).forEach(v => variables.add(JSON.stringify(v))) + + // Check headers + if (requestData.headers) { + requestData.headers.forEach(header => { + findVariables(header.key).forEach(v => variables.add(JSON.stringify(v))) + findVariables(header.value).forEach(v => variables.add(JSON.stringify(v))) + }) + } + + // Check params + if (requestData.params) { + requestData.params.forEach(param => { + findVariables(param.key).forEach(v => variables.add(JSON.stringify(v))) + findVariables(param.value).forEach(v => variables.add(JSON.stringify(v))) + }) + } + + // Check body + if (requestData.body?.raw) { + findVariables(requestData.body.raw).forEach(v => variables.add(JSON.stringify(v))) + } + + return Array.from(variables).map(v => JSON.parse(v)) + } + + return { + currentEnvironment, + variablesMap, + substituteVariables, + processRequest, + findVariables, + getRequestVariables + } +} \ No newline at end of file diff --git a/docs/Current Implementation Analysis (first g.ini b/docs/Current Implementation Analysis (first g.ini new file mode 100644 index 0000000..35cb967 --- /dev/null +++ b/docs/Current Implementation Analysis (first g.ini @@ -0,0 +1,79 @@ +Current Implementation Analysis (first generation) +✅ Features Currently Implemented: +✅ HTTP Methods - Full support (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS) +✅ URL Input - With auto-protocol fixing (google.com → https://google.com) +✅ Query Parameters - Key-value UI with checkboxes +✅ Custom Headers - Key-value UI with checkboxes +✅ Basic Authentication - Bearer Token, Basic Auth, API Key (header/query) +✅ Raw Body Input - Basic textarea with JSON detection +✅ URL-encoded Body - Key-value form +✅ Response Viewer - Body and Headers tabs +✅ Response Timing - Shows ms and bytes +✅ Status Badges - Color-coded status indicators +✅ Loading States - Button loading, proper UX +✅ Error Handling - Network errors, timeouts, invalid URLs +✅ Notifications - Success/error toasts +✅ Request History - In-memory tracking +✅ Responsive Layout - Fixed viewport constraints +⚠️ Partially Implemented: +⚠️ Collections - Data structure exists but no UI/persistence +⚠️ Environments - Data structure exists but no UI/persistence +⚠️ Form-data Body - Basic structure but no file uploads +⚠️ JSON Editor - Basic textarea, no beautify/minify +❌ Missing Critical Features: +❌ OAuth2 Flow - Complete authorization flow +❌ File Uploads - Form-data with file input +❌ Code Generation - curl, axios, fetch snippets +❌ Response Export - JSON, CSV download +❌ Test Scripts - pm.test-like functionality +❌ Import/Export - Postman collections, OpenAPI +❌ Environment Variables - {{variable}} substitution +❌ Response Timeline - Performance waterfall +❌ Cookies Tab - Cookie management +❌ Test Results Tab - Script execution results +❌ JSON Beautify/Copy - Formatting tools +❌ GraphQL Support - Query editor +❌ XML/HTML Support - Syntax highlighting +❌ Collections Sidebar - Tree view, organization +❌ Environment Selector - Dropdown switcher + +🚀 Phase-by-Phase Implementation Roadmap + +Phase 2: Enhanced Body Types & JSON Tools +Goal: Complete the request body functionality +Missing Components: +Form-data with File Upload +JSON Beautify/Minify/Copy buttons +Raw body type selector (JSON, XML, Text, HTML) +Syntax highlighting for JSON/XML +Implementation Plan: +Add file input to form-data +Add JSON formatting toolbar +Add body type sub-selector +Integrate Monaco or Ace editor + +Phase 3: Collections & Environment Management +Goal: Persistent data management +Missing Components: +Collections sidebar with tree view +Save/Load requests to collections +Environment selector dropdown +Variable substitution ({{baseUrl}}) +Persistence layer (localStorage/backend) + +Phase 4: Advanced Features +Goal: Power user functionality +Missing Components: +OAuth2 complete flow +Code generation modal +Import/Export functionality +Test scripts tab with execution +Response export options + +Phase 5: Enhanced Response Viewer +Goal: Better analysis tools +Missing Components: +Timeline tab with waterfall +Cookies tab with management +Test Results tab +Response search/filter \ No newline at end of file diff --git a/navigation/index.js b/navigation/index.js index ed66b1c..a224150 100644 --- a/navigation/index.js +++ b/navigation/index.js @@ -17,6 +17,12 @@ export default [ "header": "Pentadbiran", "description": "Urus aplikasi anda", "child": [ + { + "title": "API Platform", + "path": "/api-platform", + "icon": "", + "child": [] + }, { "title": "Konfigurasi", "icon": "ic:outline-settings", diff --git a/package.json b/package.json index 4575231..269d72b 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@kiwicom/eslint-config": "^12.7.3", "@pinia/nuxt": "^0.4.11", "@popperjs/core": "^2.11.8", - "@prisma/client": "^5.1.1", + "@prisma/client": "6.8.2", "@shimyshack/uid": "^0.1.7", "@sweetalert2/theme-dark": "^5.0.14", "@vueup/vue-quill": "^1.0.0", @@ -61,7 +61,7 @@ "maska": "^1.5.0", "pinia": "^2.1.6", "prettier": "^2.8.1", - "prisma": "^5.1.1", + "prisma": "6.8.2", "sass": "^1.62.0", "swiper": "^8.4.4", "thememirror": "^2.0.1", diff --git a/pages/api-platform/index.vue b/pages/api-platform/index.vue new file mode 100644 index 0000000..8f34ac5 --- /dev/null +++ b/pages/api-platform/index.vue @@ -0,0 +1,188 @@ + + + \ No newline at end of file diff --git a/pages/oauth/callback.vue b/pages/oauth/callback.vue new file mode 100644 index 0000000..1ac8b93 --- /dev/null +++ b/pages/oauth/callback.vue @@ -0,0 +1,88 @@ + + + \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 34d2c01..a3dadf1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -99,3 +99,235 @@ model site_settings { settingModifiedDate DateTime? @db.DateTime(0) siteLoginLogo String? @db.VarChar(500) } + +/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. +model notification_analytics { + id String @id @default(dbgenerated("(uuid())")) @db.VarChar(36) + notification_id String @db.VarChar(36) + channel_type String @db.VarChar(20) + metric_type String @db.VarChar(30) + metric_value Int? @default(0) + recorded_at DateTime? @default(now()) @db.Timestamp(0) + metadata Json? @default(dbgenerated("(_utf8mb4\\'{}\\')")) + notifications notifications @relation(fields: [notification_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "notification_analytics_ibfk_1") + + @@index([notification_id], map: "idx_notification_analytics_notification_id") +} + +model notification_categories { + id String @id @default(dbgenerated("(uuid())")) @db.VarChar(36) + name String @db.VarChar(100) + value String @unique(map: "value") @db.VarChar(50) + description String? @db.Text + created_at DateTime? @default(now()) @db.Timestamp(0) + updated_at DateTime? @default(now()) @db.Timestamp(0) + notifications notifications[] +} + +/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. +model notification_channels { + id String @id @default(dbgenerated("(uuid())")) @db.VarChar(36) + notification_id String @db.VarChar(36) + channel_type String @db.VarChar(20) + is_enabled Boolean? @default(true) + created_at DateTime? @default(now()) @db.Timestamp(0) + notifications notifications @relation(fields: [notification_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "notification_channels_ibfk_1") + + @@index([notification_id], map: "notification_id") +} + +model notification_delivery { + id Int @id @default(autoincrement()) + notification_id Int + channel_type String + recipient String + is_success Boolean @default(false) + error_message String? @db.Text + attempts Int @default(0) + sent_at DateTime? + delivered_at DateTime? + created_at DateTime @default(now()) + updated_at DateTime +} + +model notification_delivery_config { + id Int @id @default(autoincrement()) + channel_type String @unique + is_enabled Boolean @default(false) + provider String + provider_config Json @default(dbgenerated("(_utf8mb4\\'{}\\')")) + status String @default("Not Configured") + success_rate Float @default(0) @db.Float + created_at DateTime @default(now()) + updated_at DateTime + created_by Int + updated_by Int +} + +model notification_delivery_settings { + id Int @id @default(1) + auto_retry Boolean @default(true) + enable_fallback Boolean @default(true) + max_retries Int @default(3) + retry_delay Int @default(30) + priority String @default("normal") + enable_reports Boolean @default(true) + created_at DateTime @default(now()) + updated_at DateTime + created_by Int + updated_by Int +} + +/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. +model notification_queue { + id String @id @default(dbgenerated("(uuid())")) @db.VarChar(36) + notification_id String @db.VarChar(36) + recipient_id String @db.VarChar(36) + scheduled_for DateTime @db.Timestamp(0) + priority Int? @default(5) + attempts Int? @default(0) + max_attempts Int? @default(3) + status String? @default("queued") @db.VarChar(20) + last_attempt_at DateTime? @db.Timestamp(0) + error_message String? @db.Text + created_at DateTime? @default(now()) @db.Timestamp(0) + updated_at DateTime? @default(now()) @db.Timestamp(0) + notifications notifications @relation(fields: [notification_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "notification_queue_ibfk_1") + notification_recipients notification_recipients @relation(fields: [recipient_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "notification_queue_ibfk_2") + + @@index([scheduled_for], map: "idx_notification_queue_scheduled_for") + @@index([status], map: "idx_notification_queue_status") + @@index([notification_id], map: "notification_id") + @@index([recipient_id], map: "recipient_id") +} + +/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. +model notification_recipients { + id String @id @default(dbgenerated("(uuid())")) @db.VarChar(36) + notification_id String @db.VarChar(36) + user_id String @db.VarChar(36) + email String? @db.VarChar(255) + channel_type String @db.VarChar(20) + status String? @default("pending") @db.VarChar(20) + sent_at DateTime? @db.Timestamp(0) + delivered_at DateTime? @db.Timestamp(0) + opened_at DateTime? @db.Timestamp(0) + clicked_at DateTime? @db.Timestamp(0) + error_message String? @db.Text + ab_test_variant String? @db.VarChar(1) + created_at DateTime? @default(now()) @db.Timestamp(0) + updated_at DateTime? @default(now()) @db.Timestamp(0) + notification_queue notification_queue[] + notifications notifications @relation(fields: [notification_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "notification_recipients_ibfk_1") + + @@index([status], map: "idx_notification_recipients_status") + @@index([user_id], map: "idx_notification_recipients_user_id") + @@index([notification_id], map: "notification_id") +} + +model notification_templates { + id String @id @default(dbgenerated("(uuid())")) @db.VarChar(36) + name String @db.VarChar(100) + value String @unique(map: "value") @db.VarChar(50) + subject String? @db.VarChar(255) + email_content String? @db.Text + push_title String? @db.VarChar(100) + push_body String? @db.VarChar(300) + variables Json? @default(dbgenerated("(_utf8mb4\\'[]\\')")) + is_active Boolean? @default(true) + created_at DateTime? @default(now()) @db.Timestamp(0) + updated_at DateTime? @default(now()) @db.Timestamp(0) + notifications notifications[] +} + +model notification_user_segments { + id String @id @default(dbgenerated("(uuid())")) @db.VarChar(36) + notification_id String @db.VarChar(36) + segment_id String @db.VarChar(36) + created_at DateTime? @default(now()) @db.Timestamp(0) + notifications notifications @relation(fields: [notification_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "notification_user_segments_ibfk_1") + user_segments user_segments @relation(fields: [segment_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "notification_user_segments_ibfk_2") + + @@index([notification_id], map: "notification_id") + @@index([segment_id], map: "segment_id") +} + +/// This table contains check constraints and requires additional setup for migrations. Visit https://pris.ly/d/check-constraints for more info. +model notifications { + id String @id @default(dbgenerated("(uuid())")) @db.VarChar(36) + title String @db.VarChar(255) + type String @db.VarChar(20) + priority String @db.VarChar(20) + category_id String? @db.VarChar(36) + status String? @default("draft") @db.VarChar(20) + delivery_type String @db.VarChar(20) + scheduled_at DateTime? @db.Timestamp(0) + timezone String? @default("UTC") @db.VarChar(50) + expires_at DateTime? @db.Timestamp(0) + enable_ab_testing Boolean? @default(false) + ab_test_split Int? @default(50) + ab_test_name String? @db.VarChar(100) + enable_tracking Boolean? @default(true) + audience_type String @db.VarChar(20) + specific_users String? @db.Text + user_status String? @db.VarChar(20) + registration_period String? @db.VarChar(50) + exclude_unsubscribed Boolean? @default(true) + respect_do_not_disturb Boolean? @default(true) + content_type String @db.VarChar(20) + template_id String? @db.VarChar(36) + email_subject String? @db.VarChar(255) + email_content String? @db.Text + call_to_action_text String? @db.VarChar(100) + call_to_action_url String? @db.Text + push_title String? @db.VarChar(100) + push_body String? @db.VarChar(300) + push_image_url String? @db.Text + estimated_reach Int? @default(0) + actual_sent Int? @default(0) + created_by String @db.VarChar(36) + created_at DateTime? @default(now()) @db.Timestamp(0) + updated_at DateTime? @default(now()) @db.Timestamp(0) + sent_at DateTime? @db.Timestamp(0) + notification_analytics notification_analytics[] + notification_channels notification_channels[] + notification_queue notification_queue[] + notification_recipients notification_recipients[] + notification_user_segments notification_user_segments[] + notification_categories notification_categories? @relation(fields: [category_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "notifications_ibfk_1") + notification_templates notification_templates? @relation(fields: [template_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "notifications_ibfk_2") + + @@index([category_id], map: "category_id") + @@index([created_by], map: "idx_notifications_created_by") + @@index([scheduled_at], map: "idx_notifications_scheduled_at") + @@index([status], map: "idx_notifications_status") + @@index([template_id], map: "template_id") +} + +model user_notification_preferences { + id String @id @default(dbgenerated("(uuid())")) @db.VarChar(36) + user_id String @db.VarChar(36) + channel_type String @db.VarChar(20) + category_value String? @db.VarChar(50) + is_enabled Boolean? @default(true) + do_not_disturb_start DateTime? @db.Time(0) + do_not_disturb_end DateTime? @db.Time(0) + timezone String? @default("UTC") @db.VarChar(50) + created_at DateTime? @default(now()) @db.Timestamp(0) + updated_at DateTime? @default(now()) @db.Timestamp(0) + + @@unique([user_id, channel_type, category_value], map: "user_id") + @@index([user_id], map: "idx_user_notification_preferences_user_id") +} + +model user_segments { + id String @id @default(dbgenerated("(uuid())")) @db.VarChar(36) + name String @db.VarChar(100) + value String @unique(map: "value") @db.VarChar(50) + description String? @db.Text + criteria Json + is_active Boolean? @default(true) + created_at DateTime? @default(now()) @db.Timestamp(0) + updated_at DateTime? @default(now()) @db.Timestamp(0) + notification_user_segments notification_user_segments[] +} diff --git a/server/api/api-platform/send-request.post.js b/server/api/api-platform/send-request.post.js new file mode 100644 index 0000000..aee8ec8 --- /dev/null +++ b/server/api/api-platform/send-request.post.js @@ -0,0 +1,267 @@ +export default defineEventHandler(async (event) => { + try { + const requestData = await readBody(event); + + if (!requestData.url) { + return { + statusCode: 400, + message: "URL is required", + }; + } + + let { + url, + method = 'GET', + headers = {}, + params = [], + auth = {}, + requestBody = {}, + timeout = 30000 + } = requestData; + + // Fix URL if it doesn't have protocol + if (!url.startsWith('http://') && !url.startsWith('https://')) { + url = 'https://' + url; + } + + const startTime = Date.now(); + + // Build final URL with query parameters + let urlObj; + try { + urlObj = new URL(url); + } catch (error) { + return { + statusCode: 400, + message: "Invalid URL format", + data: { + status: 400, + statusText: 'Bad Request', + headers: {}, + data: { error: 'Invalid URL format. Please check the URL and try again.' }, + time: 0, + size: 0 + } + }; + } + + // Add active query parameters + params.forEach(param => { + if (param.active && param.key && param.value) { + urlObj.searchParams.set(param.key, param.value); + } + }); + + // Add API key to query if specified + if (auth.type === 'apiKey' && auth.apiKey?.key && auth.apiKey?.value && auth.apiKey?.addTo === 'query') { + urlObj.searchParams.set(auth.apiKey.key, auth.apiKey.value); + } + + const finalUrl = urlObj.toString(); + + // Build headers + const requestHeaders = {}; + + // Add custom headers + headers.forEach(header => { + if (header.active && header.key && header.value) { + requestHeaders[header.key] = header.value; + } + }); + + // Add authentication headers + if (auth.type === 'bearer' && auth.bearer) { + requestHeaders['Authorization'] = `Bearer ${auth.bearer}`; + } else if (auth.type === 'basic' && auth.basic?.username && auth.basic?.password) { + const credentials = Buffer.from(`${auth.basic.username}:${auth.basic.password}`).toString('base64'); + requestHeaders['Authorization'] = `Basic ${credentials}`; + } else if (auth.type === 'apiKey' && auth.apiKey?.key && auth.apiKey?.value && auth.apiKey?.addTo === 'header') { + requestHeaders[auth.apiKey.key] = auth.apiKey.value; + } else if (auth.type === 'oauth2' && auth.oauth2?.accessToken) { + requestHeaders['Authorization'] = `${auth.oauth2.tokenType || 'Bearer'} ${auth.oauth2.accessToken}`; + } + + // Build request body + let requestBodyData = null; + let contentType = null; + + if (method !== 'GET' && method !== 'HEAD' && requestBody.type && requestBody.type !== 'none') { + if (requestBody.type === 'raw' && requestBody.raw) { + requestBodyData = requestBody.raw; + // Try to parse as JSON to set appropriate content type + try { + JSON.parse(requestBody.raw); + contentType = 'application/json'; + } catch (e) { + contentType = 'text/plain'; + } + } else if (requestBody.type === 'x-www-form-urlencoded' && requestBody.urlEncoded) { + const urlEncodedData = new URLSearchParams(); + requestBody.urlEncoded.forEach(item => { + if (item.active && item.key && item.value) { + urlEncodedData.append(item.key, item.value); + } + }); + requestBodyData = urlEncodedData.toString(); + contentType = 'application/x-www-form-urlencoded'; + } else if (requestBody.type === 'form-data' && requestBody.formData) { + // For form-data, we'll handle files as base64 for now + // In a real implementation, you'd want multipart/form-data support + const formData = new URLSearchParams(); + let hasFiles = false; + + requestBody.formData.forEach(item => { + if (item.active && item.key) { + if (item.type === 'text' && item.value) { + formData.append(item.key, item.value); + } else if (item.type === 'file' && item.file) { + // For now, we'll send file name as value + // Real file upload would need special handling + formData.append(item.key, `[FILE: ${item.file.name}]`); + hasFiles = true; + } + } + }); + + if (hasFiles) { + // Log a warning that file uploads aren't fully supported yet + console.warn('File uploads in form-data are not fully supported in the backend proxy yet'); + } + + requestBodyData = formData.toString(); + contentType = 'application/x-www-form-urlencoded'; // Fallback to URL-encoded for now + } + } + + // Set content type if not already set + if (contentType && !requestHeaders['Content-Type'] && !requestHeaders['content-type']) { + requestHeaders['Content-Type'] = contentType; + } + + // Create fetch options + const fetchOptions = { + method, + headers: requestHeaders, + signal: AbortSignal.timeout(timeout) + }; + + if (requestBodyData) { + fetchOptions.body = requestBodyData; + } + + // Make the actual HTTP request + const response = await fetch(finalUrl, fetchOptions); + + const endTime = Date.now(); + const responseTime = endTime - startTime; + + // Get response data + const responseHeaders = {}; + response.headers.forEach((value, key) => { + responseHeaders[key] = value; + }); + + let responseData; + const contentTypeHeader = response.headers.get('content-type') || ''; + + if (contentTypeHeader.includes('application/json')) { + try { + responseData = await response.json(); + } catch (e) { + responseData = await response.text(); + } + } else if (contentTypeHeader.includes('text/') || contentTypeHeader.includes('application/xml')) { + responseData = await response.text(); + } else { + // For binary data, convert to text representation + try { + responseData = await response.text(); + } catch (e) { + responseData = '[Binary data]'; + } + } + + // Calculate response size (approximate) + const responseSize = typeof responseData === 'string' + ? new Blob([responseData]).size + : new Blob([JSON.stringify(responseData)]).size; + + return { + statusCode: 200, + message: "Request completed", + data: { + status: response.status, + statusText: response.statusText, + headers: responseHeaders, + data: responseData, + time: responseTime, + size: responseSize, + url: finalUrl + } + }; + + } catch (error) { + console.error('API Platform Request Error:', error); + + const endTime = Date.now(); + + // Handle different types of errors + if (error.name === 'AbortError') { + return { + statusCode: 408, + message: "Request timeout", + data: { + status: 408, + statusText: 'Request Timeout', + headers: {}, + data: { error: 'Request timed out' }, + time: 30000, + size: 0 + } + }; + } + + if (error.name === 'TypeError' && error.message.includes('fetch')) { + return { + statusCode: 400, + message: "Invalid URL or network error", + data: { + status: 400, + statusText: 'Bad Request', + headers: {}, + data: { error: 'Invalid URL format or network error' }, + time: endTime - Date.now() + 1000, + size: 0 + } + }; + } + + if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') { + return { + statusCode: 502, + message: "Connection error", + data: { + status: 502, + statusText: 'Bad Gateway', + headers: {}, + data: { error: 'Failed to connect to the server' }, + time: endTime - Date.now() + 1000, + size: 0 + } + }; + } + + return { + statusCode: 500, + message: "Internal server error", + data: { + status: 500, + statusText: 'Internal Server Error', + headers: {}, + data: { error: error.message || 'Something went wrong' }, + time: endTime - Date.now() + 1000, + size: 0 + } + }; + } +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d94ec08..e856722 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2039,22 +2039,53 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== -"@prisma/client@^5.1.1": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.1.1.tgz#ea2b0c8599bdb3f86d92e8df46fba795a744db01" - integrity sha512-fxcCeK5pMQGcgCqCrWsi+I2rpIbk0rAhdrN+ke7f34tIrgPwA68ensrpin+9+fZvuV2OtzHmuipwduSY6HswdA== +"@prisma/client@6.8.2": + version "6.8.2" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-6.8.2.tgz#f0af46643604cd38341f285483edc3c3ae7e6651" + integrity sha512-5II+vbyzv4si6Yunwgkj0qT/iY0zyspttoDrL3R4BYgLdp42/d2C8xdi9vqkrYtKt9H32oFIukvyw3Koz5JoDg== + +"@prisma/config@6.8.2": + version "6.8.2" + resolved "https://registry.yarnpkg.com/@prisma/config/-/config-6.8.2.tgz#30459b86f67acbd65e1e6f491215f6da9e0a2e99" + integrity sha512-ZJY1fF4qRBPdLQ/60wxNtX+eu89c3AkYEcP7L3jkp0IPXCNphCYxikTg55kPJLDOG6P0X+QG5tCv6CmsBRZWFQ== dependencies: - "@prisma/engines-version" "5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e" + jiti "2.4.2" -"@prisma/engines-version@5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e": - version "5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e.tgz#2e8a1f098ec09452dbe00923b24f582f95d1747c" - integrity sha512-owZqbY/wucbr65bXJ/ljrHPgQU5xXTSkmcE/JcbqE1kusuAXV/TLN3/exmz21SZ5rJ7WDkyk70J2G/n68iogbQ== +"@prisma/debug@6.8.2": + version "6.8.2" + resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-6.8.2.tgz#59fb9e0ccb0f431fe7011c36c95f9bfcbe051749" + integrity sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg== -"@prisma/engines@5.1.1": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.1.1.tgz#9c26d209f93a563e048eab63b1976f222f1707d0" - integrity sha512-NV/4nVNWFZSJCCIA3HIFJbbDKO/NARc9ej0tX5S9k2EVbkrFJC4Xt9b0u4rNZWL4V+F5LAjvta8vzEUw0rw+HA== +"@prisma/engines-version@6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e": + version "6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e.tgz#fd5885f4c502721ec3f81ad13d4d896d709cc34b" + integrity sha512-Rkik9lMyHpFNGaLpPF3H5q5TQTkm/aE7DsGM5m92FZTvWQsvmi6Va8On3pWvqLHOt5aPUvFb/FeZTmphI4CPiQ== + +"@prisma/engines@6.8.2": + version "6.8.2" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-6.8.2.tgz#34ef401027a38455244c23deb298e226914de97c" + integrity sha512-XqAJ//LXjqYRQ1RRabs79KOY4+v6gZOGzbcwDQl0D6n9WBKjV7qdrbd042CwSK0v0lM9MSHsbcFnU2Yn7z8Zlw== + dependencies: + "@prisma/debug" "6.8.2" + "@prisma/engines-version" "6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e" + "@prisma/fetch-engine" "6.8.2" + "@prisma/get-platform" "6.8.2" + +"@prisma/fetch-engine@6.8.2": + version "6.8.2" + resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-6.8.2.tgz#34dce0efae20199f89e59953a2e9d469746c058f" + integrity sha512-lCvikWOgaLOfqXGacEKSNeenvj0n3qR5QvZUOmPE2e1Eh8cMYSobxonCg9rqM6FSdTfbpqp9xwhSAOYfNqSW0g== + dependencies: + "@prisma/debug" "6.8.2" + "@prisma/engines-version" "6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e" + "@prisma/get-platform" "6.8.2" + +"@prisma/get-platform@6.8.2": + version "6.8.2" + resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-6.8.2.tgz#a6509de61ceab4fca80616b7e8d73705b2705a72" + integrity sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow== + dependencies: + "@prisma/debug" "6.8.2" "@rollup/plugin-alias@^5.0.0": version "5.0.0" @@ -5672,6 +5703,11 @@ jest-worker@^26.2.1: merge-stream "^2.0.0" supports-color "^7.0.0" +jiti@2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560" + integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== + jiti@^1.18.2, jiti@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.19.1.tgz#fa99e4b76a23053e0e7cde098efe1704a14c16f1" @@ -7705,12 +7741,13 @@ prism-es6@^1.2.0: resolved "https://registry.yarnpkg.com/prism-es6/-/prism-es6-1.2.0.tgz#ead4e0d7809fefc36b9636f1ea4b2cebad074e63" integrity sha512-A8JV9G2zKM8PWksT7YJcmnaWtYO6C9hSfxM/xv0RxB2aNc8rjv30WakzIw1gWyqLi2eiqquo2KmS7orxqlm+yg== -prisma@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.1.1.tgz#8f5c0f9467a828746cb94f846d694dc7b7481a9e" - integrity sha512-WJFG/U7sMmcc6TjJTTifTfpI6Wjoh55xl4AzopVwAdyK68L9/ogNo8QQ2cxuUjJf/Wa82z/uhyh3wMzvRIBphg== +prisma@6.8.2: + version "6.8.2" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-6.8.2.tgz#5cd9e1635b6ed0e27ea3cf3ef31c648c55115a63" + integrity sha512-JNricTXQxzDtRS7lCGGOB4g5DJ91eg3nozdubXze3LpcMl1oWwcFddrj++Up3jnRE6X/3gB/xz3V+ecBk/eEGA== dependencies: - "@prisma/engines" "5.1.1" + "@prisma/config" "6.8.2" + "@prisma/engines" "6.8.2" process-nextick-args@~2.0.0: version "2.0.1"