Compare commits
2 Commits
9b65ba97b2
...
1408fbfd9d
Author | SHA1 | Date | |
---|---|---|---|
1408fbfd9d | |||
6b7cd30754 |
351
components/api-platform/ScalarConfigModal.vue
Normal file
351
components/api-platform/ScalarConfigModal.vue
Normal file
@ -0,0 +1,351 @@
|
||||
<template>
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-4xl max-h-[90vh] flex flex-col">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="flex items-center gap-3">
|
||||
<Icon name="ic:outline-settings" size="24" class="text-primary" />
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
API Documentation Settings
|
||||
</h2>
|
||||
</div>
|
||||
<rs-button variant="text" size="sm" @click="$emit('close')">
|
||||
<Icon name="ic:outline-close" size="20" />
|
||||
</rs-button>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 overflow-hidden flex">
|
||||
<!-- Sidebar Navigation -->
|
||||
<div class="w-64 bg-gray-50 dark:bg-gray-900 border-r border-gray-200 dark:border-gray-700 p-4">
|
||||
<nav class="space-y-2">
|
||||
<button
|
||||
v-for="tab in configTabs"
|
||||
:key="tab.id"
|
||||
@click="activeTab = tab.id"
|
||||
class="w-full text-left px-3 py-2 rounded-lg text-sm font-medium transition-colors"
|
||||
:class="{
|
||||
'bg-primary text-white': activeTab === tab.id,
|
||||
'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800': activeTab !== tab.id
|
||||
}"
|
||||
>
|
||||
<Icon :name="tab.icon" size="16" class="inline mr-2" />
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 p-6 overflow-y-auto">
|
||||
<!-- Basic Settings -->
|
||||
<div v-if="activeTab === 'basic'" class="space-y-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Basic Information</h3>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<FormKit
|
||||
type="text"
|
||||
label="API Title"
|
||||
help="The title shown in the documentation header"
|
||||
v-model="localConfig.title"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="textarea"
|
||||
label="Description"
|
||||
help="Brief description of your API"
|
||||
v-model="localConfig.description"
|
||||
rows="3"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Version"
|
||||
help="API version number"
|
||||
v-model="localConfig.version"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">OpenAPI Source</h3>
|
||||
<FormKit
|
||||
type="select"
|
||||
label="Source Type"
|
||||
help="Choose the source of your OpenAPI specification"
|
||||
v-model="localConfig.sourceType"
|
||||
:options="availableSourceTypes.map(t => ({ label: `${t.label} - ${t.description}`, value: t.value }))"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Display Settings -->
|
||||
<div v-if="activeTab === 'display'" class="space-y-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Theme</h3>
|
||||
<FormKit
|
||||
type="select"
|
||||
label="Color Theme"
|
||||
help="Choose the color theme for the documentation"
|
||||
v-model="localConfig.theme"
|
||||
:options="availableThemes.map(t => ({ label: `${t.label} - ${t.description}`, value: t.value }))"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Layout Options</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<div>
|
||||
<h5 class="font-medium text-gray-900 dark:text-white">Show Sidebar</h5>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Display the navigation sidebar</p>
|
||||
</div>
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
v-model="localConfig.showSidebar"
|
||||
outer-class="!mb-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<div>
|
||||
<h5 class="font-medium text-gray-900 dark:text-white">Show Search</h5>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Enable search functionality</p>
|
||||
</div>
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
v-model="localConfig.showSearch"
|
||||
outer-class="!mb-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<div>
|
||||
<h5 class="font-medium text-gray-900 dark:text-white">Expand Tags by Default</h5>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Show all endpoint groups expanded on load</p>
|
||||
</div>
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
v-model="localConfig.defaultExpandedTags"
|
||||
outer-class="!mb-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<div>
|
||||
<h5 class="font-medium text-gray-900 dark:text-white">Group by Tags</h5>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Organize endpoints by their tags</p>
|
||||
</div>
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
v-model="localConfig.groupByTags"
|
||||
outer-class="!mb-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Settings -->
|
||||
<div v-if="activeTab === 'content'" class="space-y-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Content Display</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<div>
|
||||
<h5 class="font-medium text-gray-900 dark:text-white">Show Parameters</h5>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Display endpoint parameters section</p>
|
||||
</div>
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
v-model="localConfig.showParameters"
|
||||
outer-class="!mb-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<div>
|
||||
<h5 class="font-medium text-gray-900 dark:text-white">Show Request Body</h5>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Display request body schemas and examples</p>
|
||||
</div>
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
v-model="localConfig.showRequestBody"
|
||||
outer-class="!mb-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<div>
|
||||
<h5 class="font-medium text-gray-900 dark:text-white">Show Responses</h5>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Display response schemas and examples</p>
|
||||
</div>
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
v-model="localConfig.showResponses"
|
||||
outer-class="!mb-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-900 rounded-lg">
|
||||
<div>
|
||||
<h5 class="font-medium text-gray-900 dark:text-white">Show Examples</h5>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Display code examples and sample data</p>
|
||||
</div>
|
||||
<FormKit
|
||||
type="checkbox"
|
||||
v-model="localConfig.showExamples"
|
||||
outer-class="!mb-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Settings -->
|
||||
<div v-if="activeTab === 'contact'" class="space-y-6">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Contact Information</h3>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Contact Name"
|
||||
help="Name or team responsible for the API"
|
||||
v-model="localConfig.contact.name"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="email"
|
||||
label="Contact Email"
|
||||
help="Email address for API support"
|
||||
v-model="localConfig.contact.email"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="url"
|
||||
label="Contact URL"
|
||||
help="Website or documentation URL"
|
||||
v-model="localConfig.contact.url"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Metadata</h3>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Page Title"
|
||||
help="Browser tab title"
|
||||
v-model="localConfig.metaData.title"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="textarea"
|
||||
label="Meta Description"
|
||||
help="SEO description for the documentation page"
|
||||
v-model="localConfig.metaData.description"
|
||||
rows="2"
|
||||
/>
|
||||
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Favicon URL"
|
||||
help="Path to favicon icon"
|
||||
v-model="localConfig.metaData.favicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex items-center justify-between p-6 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
|
||||
<div class="flex items-center space-x-3">
|
||||
<rs-button
|
||||
variant="secondary-outline"
|
||||
size="sm"
|
||||
@click="resetConfiguration"
|
||||
>
|
||||
<Icon name="ic:outline-refresh" size="16" class="mr-1" />
|
||||
Reset to Defaults
|
||||
</rs-button>
|
||||
|
||||
<rs-button
|
||||
variant="secondary-outline"
|
||||
size="sm"
|
||||
@click="previewConfiguration"
|
||||
>
|
||||
<Icon name="ic:outline-visibility" size="16" class="mr-1" />
|
||||
Preview
|
||||
</rs-button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-3">
|
||||
<rs-button variant="secondary-outline" @click="$emit('close')">
|
||||
Cancel
|
||||
</rs-button>
|
||||
<rs-button variant="primary" @click="saveConfiguration">
|
||||
<Icon name="ic:outline-save" size="16" class="mr-1" />
|
||||
Save Changes
|
||||
</rs-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const emit = defineEmits(['close', 'saved']);
|
||||
|
||||
// Configuration management
|
||||
const {
|
||||
scalarConfig,
|
||||
availableThemes,
|
||||
availableSourceTypes,
|
||||
updateScalarConfig,
|
||||
resetScalarConfig
|
||||
} = useScalarConfig();
|
||||
|
||||
// Local state
|
||||
const activeTab = ref('basic');
|
||||
const localConfig = ref({});
|
||||
|
||||
// Tab configuration
|
||||
const configTabs = [
|
||||
{ id: 'basic', label: 'Basic Info', icon: 'ic:outline-info' },
|
||||
{ id: 'display', label: 'Display', icon: 'ic:outline-palette' },
|
||||
{ id: 'content', label: 'Content', icon: 'ic:outline-article' },
|
||||
{ id: 'contact', label: 'Contact', icon: 'ic:outline-contact-mail' }
|
||||
];
|
||||
|
||||
// Initialize local config
|
||||
onMounted(() => {
|
||||
localConfig.value = JSON.parse(JSON.stringify(scalarConfig.value));
|
||||
});
|
||||
|
||||
// Save configuration
|
||||
const saveConfiguration = () => {
|
||||
updateScalarConfig(localConfig.value);
|
||||
emit('saved');
|
||||
emit('close');
|
||||
};
|
||||
|
||||
// Reset configuration
|
||||
const resetConfiguration = () => {
|
||||
if (confirm('Are you sure you want to reset all settings to their defaults?')) {
|
||||
resetScalarConfig();
|
||||
localConfig.value = JSON.parse(JSON.stringify(scalarConfig.value));
|
||||
}
|
||||
};
|
||||
|
||||
// Preview configuration
|
||||
const previewConfiguration = () => {
|
||||
// Save current config to preview storage
|
||||
localStorage.setItem('api-docs-preview-config', JSON.stringify(localConfig.value));
|
||||
|
||||
// Open preview in new tab
|
||||
const previewUrl = '/api-docs?preview=true';
|
||||
window.open(previewUrl, '_blank');
|
||||
};
|
||||
</script>
|
209
composables/useScalarConfig.js
Normal file
209
composables/useScalarConfig.js
Normal file
@ -0,0 +1,209 @@
|
||||
// API Documentation configuration management composable
|
||||
const apiDocsConfig = ref({
|
||||
// Basic Information
|
||||
title: 'Corrad AF 2024 API Platform',
|
||||
description: 'Complete API reference for the Corrad AF 2024 API Platform project',
|
||||
version: '2.0.0',
|
||||
|
||||
// OpenAPI Source Configuration
|
||||
sourceType: 'default', // 'default', 'collections', 'custom'
|
||||
openApiUrl: '/openapi.json', // Default main OpenAPI JSON file
|
||||
|
||||
// Theme Configuration
|
||||
theme: 'light', // light, dark, auto
|
||||
|
||||
// Features Configuration
|
||||
showSidebar: true,
|
||||
defaultExpandedTags: true,
|
||||
showSearch: true,
|
||||
|
||||
// Advanced Configuration
|
||||
metaData: {
|
||||
title: 'Corrad AF 2024 API Documentation',
|
||||
description: 'Complete API reference for all endpoints in the Corrad AF 2024 platform',
|
||||
favicon: '/favicon.ico'
|
||||
},
|
||||
|
||||
// Display Options
|
||||
groupByTags: true,
|
||||
showParameters: true,
|
||||
showRequestBody: true,
|
||||
showResponses: true,
|
||||
showExamples: true,
|
||||
|
||||
// Contact Information
|
||||
contact: {
|
||||
name: 'API Support',
|
||||
email: 'support@corradaf.com',
|
||||
url: 'https://corradaf.com'
|
||||
}
|
||||
});
|
||||
|
||||
// Available source types
|
||||
const availableSourceTypes = [
|
||||
{ value: 'default', label: 'Default OpenAPI', description: 'Main OpenAPI specification', url: '/openapi.json' },
|
||||
{ value: 'collections', label: 'Generated from Collections', description: 'Auto-generated from API collections', url: '/openapi-coll.json' },
|
||||
{ value: 'custom', label: 'Custom JSON', description: 'Custom edited OpenAPI specification', url: '/openapi-custom.json' }
|
||||
];
|
||||
|
||||
// Available theme options
|
||||
const availableThemes = [
|
||||
{ value: 'light', label: 'Light', description: 'Light theme' },
|
||||
{ value: 'dark', label: 'Dark', description: 'Dark theme' },
|
||||
{ value: 'auto', label: 'Auto', description: 'Follow system preference' }
|
||||
];
|
||||
|
||||
// Configuration loaded flag
|
||||
let configLoaded = false;
|
||||
|
||||
// Load configuration from localStorage
|
||||
const loadApiDocsConfig = () => {
|
||||
if (configLoaded) return; // Avoid loading multiple times
|
||||
|
||||
try {
|
||||
const saved = localStorage.getItem('api-docs-config');
|
||||
if (saved) {
|
||||
const parsed = JSON.parse(saved);
|
||||
// Merge with defaults to ensure all properties exist
|
||||
apiDocsConfig.value = { ...apiDocsConfig.value, ...parsed };
|
||||
}
|
||||
configLoaded = true;
|
||||
} catch (error) {
|
||||
console.error('Failed to load API docs configuration:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Save configuration to localStorage
|
||||
const saveApiDocsConfig = () => {
|
||||
try {
|
||||
localStorage.setItem('api-docs-config', JSON.stringify(apiDocsConfig.value));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to save API docs configuration:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Update specific configuration
|
||||
const updateApiDocsConfig = (updates) => {
|
||||
apiDocsConfig.value = { ...apiDocsConfig.value, ...updates };
|
||||
|
||||
// Update OpenAPI URL based on source type
|
||||
if (updates.sourceType) {
|
||||
const sourceConfig = availableSourceTypes.find(s => s.value === updates.sourceType);
|
||||
if (sourceConfig) {
|
||||
apiDocsConfig.value.openApiUrl = sourceConfig.url;
|
||||
}
|
||||
}
|
||||
|
||||
return saveApiDocsConfig();
|
||||
};
|
||||
|
||||
// Update source type and corresponding URL
|
||||
const updateSourceType = (sourceType) => {
|
||||
const sourceConfig = availableSourceTypes.find(s => s.value === sourceType);
|
||||
if (sourceConfig) {
|
||||
apiDocsConfig.value.sourceType = sourceType;
|
||||
apiDocsConfig.value.openApiUrl = sourceConfig.url;
|
||||
return saveApiDocsConfig();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Reset to defaults
|
||||
const resetApiDocsConfig = () => {
|
||||
apiDocsConfig.value = {
|
||||
title: 'Corrad AF 2024 API Platform',
|
||||
description: 'Complete API reference for the Corrad AF 2024 API Platform project',
|
||||
version: '2.0.0',
|
||||
sourceType: 'default',
|
||||
openApiUrl: '/openapi.json',
|
||||
theme: 'light',
|
||||
showSidebar: true,
|
||||
defaultExpandedTags: true,
|
||||
showSearch: true,
|
||||
metaData: {
|
||||
title: 'Corrad AF 2024 API Documentation',
|
||||
description: 'Complete API reference for all endpoints in the Corrad AF 2024 platform',
|
||||
favicon: '/favicon.ico'
|
||||
},
|
||||
groupByTags: true,
|
||||
showParameters: true,
|
||||
showRequestBody: true,
|
||||
showResponses: true,
|
||||
showExamples: true,
|
||||
contact: {
|
||||
name: 'API Support',
|
||||
email: 'support@corradaf.com',
|
||||
url: 'https://corradaf.com'
|
||||
}
|
||||
};
|
||||
configLoaded = false;
|
||||
return saveApiDocsConfig();
|
||||
};
|
||||
|
||||
// Generate API docs configuration object
|
||||
const getApiDocsConfiguration = () => {
|
||||
// Ensure config is loaded before generating
|
||||
if (!configLoaded && process.client) {
|
||||
loadApiDocsConfig();
|
||||
}
|
||||
|
||||
const config = apiDocsConfig.value;
|
||||
|
||||
// Ensure the URL is valid and use default if not
|
||||
let openApiUrl = config.openApiUrl;
|
||||
if (!openApiUrl || typeof openApiUrl !== 'string') {
|
||||
console.warn('Invalid OpenAPI URL in config, using default');
|
||||
openApiUrl = '/openapi.json';
|
||||
}
|
||||
|
||||
// Make sure URL starts with / or http(s)
|
||||
if (!openApiUrl.startsWith('/') && !openApiUrl.startsWith('http')) {
|
||||
openApiUrl = `/${openApiUrl}`;
|
||||
}
|
||||
|
||||
return {
|
||||
url: openApiUrl,
|
||||
title: config.title || 'API Documentation',
|
||||
description: config.description || '',
|
||||
version: config.version || '1.0.0',
|
||||
theme: config.theme || 'light',
|
||||
showSidebar: Boolean(config.showSidebar),
|
||||
defaultExpandedTags: Boolean(config.defaultExpandedTags),
|
||||
showSearch: Boolean(config.showSearch),
|
||||
groupByTags: Boolean(config.groupByTags),
|
||||
showParameters: Boolean(config.showParameters),
|
||||
showRequestBody: Boolean(config.showRequestBody),
|
||||
showResponses: Boolean(config.showResponses),
|
||||
showExamples: Boolean(config.showExamples),
|
||||
metaData: {
|
||||
title: config.metaData?.title || 'API Documentation',
|
||||
description: config.metaData?.description || 'Complete API reference',
|
||||
favicon: config.metaData?.favicon || '/favicon.ico'
|
||||
},
|
||||
contact: config.contact || {}
|
||||
};
|
||||
};
|
||||
|
||||
// Auto-load configuration on client side
|
||||
if (process.client) {
|
||||
loadApiDocsConfig();
|
||||
}
|
||||
|
||||
export const useScalarConfig = () => {
|
||||
return {
|
||||
// State (keeping the same export name for compatibility)
|
||||
scalarConfig: readonly(apiDocsConfig),
|
||||
availableThemes,
|
||||
availableSourceTypes,
|
||||
|
||||
// Methods (keeping the same names for compatibility)
|
||||
loadScalarConfig: loadApiDocsConfig,
|
||||
saveScalarConfig: saveApiDocsConfig,
|
||||
updateScalarConfig: updateApiDocsConfig,
|
||||
updateSourceType,
|
||||
resetScalarConfig: resetApiDocsConfig,
|
||||
getScalarConfiguration: getApiDocsConfiguration
|
||||
};
|
||||
};
|
178
docs/API_ACCESS_GUIDE.md
Normal file
178
docs/API_ACCESS_GUIDE.md
Normal file
@ -0,0 +1,178 @@
|
||||
# API Documentation Access Guide
|
||||
|
||||
This guide explains how to access and use the comprehensive API documentation for the Corrad AF 2024 API Platform.
|
||||
|
||||
## 🌐 Live API Documentation
|
||||
|
||||
### Scalar API Reference (Interactive Documentation)
|
||||
**URL:** `http://localhost:3000/api-docs`
|
||||
|
||||
- **Interactive Interface**: Modern, user-friendly API explorer
|
||||
- **Try It Out**: Test API endpoints directly from the documentation
|
||||
- **Multiple Themes**: Choose from 10+ beautiful themes
|
||||
- **Real-time Testing**: Send requests and see responses immediately
|
||||
- **Authentication**: Built-in support for Bearer token authentication
|
||||
|
||||
### OpenAPI Specification (Swagger JSON)
|
||||
**URL:** `http://localhost:3000/api/openapi`
|
||||
|
||||
- **Dynamic Server URLs**: Automatically detects your current host
|
||||
- **Complete Specification**: All 79+ endpoints documented
|
||||
- **Import Ready**: Use with Postman, Insomnia, or other API tools
|
||||
- **Cache Optimized**: 5-minute cache for better performance
|
||||
|
||||
### Static OpenAPI File
|
||||
**URL:** `http://localhost:3000/openapi.json`
|
||||
|
||||
- **Static Version**: Original OpenAPI specification file
|
||||
- **Backup Access**: Alternative if dynamic endpoint is unavailable
|
||||
|
||||
## 📱 Access Methods
|
||||
|
||||
### 1. Browser Access
|
||||
Open any web browser and navigate to:
|
||||
```
|
||||
http://localhost:3000/api-docs
|
||||
```
|
||||
|
||||
### 2. Postman Import
|
||||
1. Open Postman
|
||||
2. Click "Import" → "Link"
|
||||
3. Enter: `http://localhost:3000/api/openapi`
|
||||
4. Click "Continue" → "Import"
|
||||
|
||||
### 3. Insomnia Import
|
||||
1. Open Insomnia
|
||||
2. Click "Create" → "Import From" → "URL"
|
||||
3. Enter: `http://localhost:3000/api/openapi`
|
||||
4. Click "Fetch and Import"
|
||||
|
||||
### 4. Swagger UI
|
||||
Use any Swagger UI instance with the URL:
|
||||
```
|
||||
http://localhost:3000/api/openapi
|
||||
```
|
||||
|
||||
### 5. curl Access
|
||||
Fetch the OpenAPI specification:
|
||||
```bash
|
||||
curl http://localhost:3000/api/openapi
|
||||
```
|
||||
|
||||
## 🔧 Features
|
||||
|
||||
### Interactive Documentation
|
||||
- **Modern UI**: Clean, responsive design
|
||||
- **Code Examples**: Request/response examples for all endpoints
|
||||
- **Authentication**: Integrated login and token management
|
||||
- **Real-time Testing**: Send actual requests to the API
|
||||
- **Multiple Formats**: JSON, form-data, and URL-encoded support
|
||||
|
||||
### API Categories
|
||||
1. **Authentication** (3 endpoints)
|
||||
- Login, logout, token validation
|
||||
2. **Business Logic** (1 endpoint)
|
||||
- Asnaf profile analysis with AI
|
||||
3. **API Platform** (3 endpoints)
|
||||
- HTTP proxy, OAuth2 flows
|
||||
4. **Metabase Integration** (1 endpoint)
|
||||
- Analytics token management
|
||||
5. **Development Tools** (70+ endpoints)
|
||||
- User, role, menu management
|
||||
- Database operations
|
||||
- Content management
|
||||
- Configuration tools
|
||||
|
||||
### Authentication
|
||||
Most endpoints require a Bearer token. To authenticate:
|
||||
|
||||
1. Use the `/api/auth/login` endpoint
|
||||
2. Copy the returned token
|
||||
3. Add to Authorization header: `Bearer <your-token>`
|
||||
|
||||
## 🎨 Customization
|
||||
|
||||
### Theme Options
|
||||
Access the settings panel in the documentation to choose from:
|
||||
- Default
|
||||
- Alternate
|
||||
- Moon (Dark)
|
||||
- Purple
|
||||
- Solarized
|
||||
- Blue Planet
|
||||
- Saturn
|
||||
- Kepler
|
||||
- Mars
|
||||
- Deep Space
|
||||
|
||||
### Layout Options
|
||||
- **Modern**: Three-column layout with sidebar
|
||||
- **Classic**: Two-column traditional layout
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
1. **Start the Server**
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
2. **Access Documentation**
|
||||
Open: `http://localhost:3000/api-docs`
|
||||
|
||||
3. **Authenticate**
|
||||
- Use the login endpoint
|
||||
- Enter your credentials
|
||||
- Token will be automatically saved
|
||||
|
||||
4. **Explore APIs**
|
||||
- Browse by category
|
||||
- Try the "Send Request" button
|
||||
- View real responses
|
||||
|
||||
## 🔗 URLs Summary
|
||||
|
||||
| Service | URL | Description |
|
||||
|---------|-----|-------------|
|
||||
| **Interactive Docs** | `http://localhost:3000/api-docs` | Main API documentation interface |
|
||||
| **OpenAPI Spec** | `http://localhost:3000/api/openapi` | Dynamic OpenAPI JSON |
|
||||
| **Static Spec** | `http://localhost:3000/openapi.json` | Static OpenAPI file |
|
||||
| **Postman Collection** | `./postman_collection.json` | Ready-to-import Postman collection |
|
||||
| **Preview Mode** | `http://localhost:3000/api-docs?preview=true` | Preview with custom settings |
|
||||
|
||||
## 📋 Postman Collection
|
||||
|
||||
A complete Postman collection is available in the root directory:
|
||||
- **File**: `postman_collection.json`
|
||||
- **Endpoints**: All 79+ API endpoints
|
||||
- **Environment**: Pre-configured variables
|
||||
- **Authentication**: Automatic token management
|
||||
- **Examples**: Sample requests and responses
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### White Screen Issue (Fixed)
|
||||
- Improved CSS styling for proper height management
|
||||
- Better scrolling behavior
|
||||
- Enhanced container management
|
||||
|
||||
### Common Issues
|
||||
1. **Documentation not loading**: Check if server is running on port 3000
|
||||
2. **Authentication errors**: Ensure valid token in Authorization header
|
||||
3. **CORS issues**: Use the built-in proxy for external API calls
|
||||
4. **Theme not applying**: Clear browser cache and reload
|
||||
|
||||
### Development URLs
|
||||
Replace `localhost:3000` with your actual server URL when deployed.
|
||||
|
||||
## 📞 Support
|
||||
|
||||
For API support and questions:
|
||||
- **Email**: support@corradaf.com
|
||||
- **Documentation**: This interactive documentation
|
||||
- **Postman Collection**: Available in project root
|
||||
|
||||
---
|
||||
|
||||
**Note**: This documentation is automatically updated with server information and includes all implemented endpoints from the codebase.
|
428
docs/API_ENDPOINTS_DOCUMENTATION.md
Normal file
428
docs/API_ENDPOINTS_DOCUMENTATION.md
Normal file
@ -0,0 +1,428 @@
|
||||
# Corrad AF 2024 API Platform - Complete API Endpoints Documentation
|
||||
|
||||
This document provides a comprehensive list of all API endpoints available in the Corrad AF 2024 API Platform project.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Authentication APIs](#authentication-apis)
|
||||
2. [Business Logic APIs](#business-logic-apis)
|
||||
3. [API Platform APIs](#api-platform-apis)
|
||||
4. [Metabase Integration APIs](#metabase-integration-apis)
|
||||
5. [Development Tools APIs](#development-tools-apis)
|
||||
- [User Management](#user-management)
|
||||
- [Role Management](#role-management)
|
||||
- [Menu Management](#menu-management)
|
||||
- [ORM & Database Management](#orm--database-management)
|
||||
- [Configuration Management](#configuration-management)
|
||||
- [API Management Tools](#api-management-tools)
|
||||
- [Content Management](#content-management)
|
||||
- [Lookup Data](#lookup-data)
|
||||
|
||||
---
|
||||
|
||||
## Authentication APIs
|
||||
|
||||
### POST `/api/auth/login`
|
||||
**Description:** Authenticate user and receive access/refresh tokens
|
||||
**Parameters:**
|
||||
- `username` (string, required): User's username
|
||||
- `password` (string, required): User's password
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"statusCode": 200,
|
||||
"message": "Login success",
|
||||
"data": {
|
||||
"username": "user@example.com",
|
||||
"roles": ["admin", "user"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GET `/api/auth/logout`
|
||||
**Description:** Logout user and clear authentication cookies
|
||||
|
||||
### GET `/api/auth/validate`
|
||||
**Description:** Validate current authentication token
|
||||
|
||||
---
|
||||
|
||||
## Business Logic APIs
|
||||
|
||||
### POST `/api/analyze-asnaf`
|
||||
**Description:** Analyze Asnaf profile using AI/OpenAI integration to determine eligibility and assistance recommendations
|
||||
**Parameters:**
|
||||
- `monthlyIncome` (string): Monthly income amount
|
||||
- `otherIncome` (string): Other income sources
|
||||
- `totalIncome` (string): Total income amount
|
||||
- `occupation` (string): Applicant's occupation
|
||||
- `maritalStatus` (string): Marital status
|
||||
- `dependents` (array): List of dependents
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"hadKifayahPercentage": "75%",
|
||||
"kategoriAsnaf": "Miskin",
|
||||
"kategoriKeluarga": "Miskin (50-100% HK)",
|
||||
"cadanganKategori": "Miskin",
|
||||
"statusKelayakan": "Layak (Miskin)",
|
||||
"cadanganBantuan": [
|
||||
{"nama": "Bantuan Kewangan Bulanan", "peratusan": "90%"},
|
||||
{"nama": "Bantuan Makanan Asas", "peratusan": "75%"}
|
||||
],
|
||||
"ramalanJangkaMasaPulih": "6 bulan",
|
||||
"rumusan": "Pemohon memerlukan perhatian segera."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Platform APIs
|
||||
|
||||
### POST `/api/api-platform/send-request`
|
||||
**Description:** Proxy HTTP requests through the platform with authentication and request/response handling
|
||||
**Parameters:**
|
||||
- `url` (string, required): Target URL to send request to
|
||||
- `method` (string): HTTP method (GET, POST, PUT, DELETE, etc.)
|
||||
- `headers` (array): Request headers
|
||||
- `params` (array): Query parameters
|
||||
- `auth` (object): Authentication configuration
|
||||
- `requestBody` (object): Request body configuration
|
||||
- `timeout` (number): Request timeout in milliseconds
|
||||
|
||||
### POST `/api/api-platform/oauth2/client-credentials`
|
||||
**Description:** Obtain OAuth2 access token using client credentials flow
|
||||
**Parameters:**
|
||||
- `client_id` (string, required): OAuth2 client ID
|
||||
- `client_secret` (string, required): OAuth2 client secret
|
||||
- `grant_type` (string, required): Grant type (client_credentials)
|
||||
- `scope` (string): Requested scopes
|
||||
|
||||
### POST `/api/api-platform/oauth2/exchange-code`
|
||||
**Description:** Exchange authorization code for access token in OAuth2 flow
|
||||
**Parameters:**
|
||||
- `code` (string, required): Authorization code
|
||||
- `client_id` (string, required): OAuth2 client ID
|
||||
- `client_secret` (string, required): OAuth2 client secret
|
||||
- `redirect_uri` (string, required): Redirect URI
|
||||
|
||||
---
|
||||
|
||||
## Metabase Integration APIs
|
||||
|
||||
### GET `/api/metabase/token`
|
||||
**Description:** Get authentication token for Metabase integration
|
||||
|
||||
---
|
||||
|
||||
## Development Tools APIs
|
||||
|
||||
### User Management
|
||||
|
||||
#### GET `/api/devtool/user/list`
|
||||
**Description:** Get list of all users (excluding deleted)
|
||||
|
||||
#### POST `/api/devtool/user/add`
|
||||
**Description:** Create a new user
|
||||
**Parameters:**
|
||||
- `userUsername` (string, required): Username
|
||||
- `userFullName` (string, required): Full name
|
||||
- `userEmail` (string, required): Email address
|
||||
- `userPhone` (string): Phone number
|
||||
- `userPassword` (string, required): Password
|
||||
- `roles` (array): Assigned roles
|
||||
|
||||
#### PUT `/api/devtool/user/edit`
|
||||
**Description:** Update existing user
|
||||
**Parameters:**
|
||||
- `userID` (string, required): User ID
|
||||
- `userFullName` (string): Updated full name
|
||||
- `userEmail` (string): Updated email
|
||||
- `userPhone` (string): Updated phone
|
||||
- `roles` (array): Updated roles
|
||||
|
||||
#### DELETE `/api/devtool/user/delete`
|
||||
**Description:** Soft delete user (mark as deleted)
|
||||
**Parameters:**
|
||||
- `userID` (string, required): User ID to delete
|
||||
|
||||
### Role Management
|
||||
|
||||
#### GET `/api/devtool/role/list`
|
||||
**Description:** Get list of all roles
|
||||
|
||||
#### POST `/api/devtool/role/add`
|
||||
**Description:** Create a new role
|
||||
**Parameters:**
|
||||
- `roleName` (string, required): Role name
|
||||
- `roleDescription` (string): Role description
|
||||
|
||||
#### PUT `/api/devtool/role/edit`
|
||||
**Description:** Update existing role
|
||||
**Parameters:**
|
||||
- `roleID` (string, required): Role ID
|
||||
- `roleName` (string): Updated role name
|
||||
- `roleDescription` (string): Updated description
|
||||
|
||||
#### DELETE `/api/devtool/role/delete`
|
||||
**Description:** Delete role
|
||||
**Parameters:**
|
||||
- `roleID` (string, required): Role ID to delete
|
||||
|
||||
### Menu Management
|
||||
|
||||
#### GET `/api/devtool/menu/user-list`
|
||||
**Description:** Get menu items for users
|
||||
|
||||
#### GET `/api/devtool/menu/role-list`
|
||||
**Description:** Get menu items for roles
|
||||
|
||||
#### POST `/api/devtool/menu/add`
|
||||
**Description:** Add new menu item
|
||||
**Parameters:**
|
||||
- `title` (string, required): Menu item title
|
||||
- `path` (string, required): Menu item path
|
||||
- `icon` (string): Icon name
|
||||
- `parent` (string): Parent menu ID
|
||||
- `order` (number): Display order
|
||||
|
||||
#### PUT `/api/devtool/menu/edit`
|
||||
**Description:** Edit existing menu item
|
||||
**Parameters:**
|
||||
- `id` (string, required): Menu item ID
|
||||
- `title` (string): Updated title
|
||||
- `path` (string): Updated path
|
||||
- `icon` (string): Updated icon
|
||||
|
||||
#### DELETE `/api/devtool/menu/delete`
|
||||
**Description:** Delete menu item
|
||||
**Parameters:**
|
||||
- `id` (string, required): Menu item ID
|
||||
|
||||
#### POST `/api/devtool/menu/overwrite-navigation`
|
||||
**Description:** Overwrite entire navigation structure
|
||||
**Parameters:**
|
||||
- `navigation` (array, required): New navigation structure
|
||||
|
||||
#### POST `/api/devtool/menu/new-add`
|
||||
**Description:** New add menu functionality
|
||||
|
||||
### ORM & Database Management
|
||||
|
||||
#### GET `/api/devtool/orm/schema`
|
||||
**Description:** Get database schema information
|
||||
|
||||
#### GET `/api/devtool/orm/studio`
|
||||
**Description:** Access ORM studio interface
|
||||
|
||||
#### GET `/api/devtool/orm/data/get`
|
||||
**Description:** Get data from specific table
|
||||
**Query Parameters:**
|
||||
- `table` (string, required): Table name
|
||||
- `limit` (number): Number of records to return
|
||||
|
||||
#### GET `/api/devtool/orm/table/config`
|
||||
**Description:** Get table configuration settings
|
||||
|
||||
#### POST `/api/devtool/orm/table/create`
|
||||
**Description:** Create new database table
|
||||
**Parameters:**
|
||||
- `tableName` (string, required): New table name
|
||||
- `columns` (array, required): Column definitions
|
||||
|
||||
#### GET `/api/devtool/orm/table/modify/get`
|
||||
**Description:** Get table structure for modification
|
||||
**Query Parameters:**
|
||||
- `table` (string, required): Table name
|
||||
|
||||
#### POST `/api/devtool/orm/table/modify`
|
||||
**Description:** Modify existing table structure
|
||||
**Parameters:**
|
||||
- `tableName` (string, required): Table to modify
|
||||
- `modifications` (object, required): Modification instructions
|
||||
|
||||
#### DELETE `/api/devtool/orm/table/delete/{table}`
|
||||
**Description:** Delete table by name (dynamic route)
|
||||
**Path Parameters:**
|
||||
- `table` (string, required): Table name to delete
|
||||
|
||||
### Configuration Management
|
||||
|
||||
#### GET `/api/devtool/config/site-settings`
|
||||
**Description:** Get/manage site settings
|
||||
|
||||
#### GET `/api/devtool/config/env`
|
||||
**Description:** Get environment configuration
|
||||
|
||||
#### POST `/api/devtool/config/upload-file`
|
||||
**Description:** Upload file to server
|
||||
**Body:** multipart/form-data
|
||||
- `file` (file, required): File to upload
|
||||
- `destination` (string): Upload destination path
|
||||
|
||||
#### GET `/api/devtool/config/loading-logo`
|
||||
**Description:** Get/set loading logo configuration
|
||||
|
||||
#### POST `/api/devtool/config/add-custom-theme`
|
||||
**Description:** Add custom theme configuration
|
||||
**Parameters:**
|
||||
- `themeName` (string, required): Theme name
|
||||
- `colors` (object): Color configuration
|
||||
- `fonts` (object): Font configuration
|
||||
|
||||
### API Management Tools
|
||||
|
||||
#### GET `/api/devtool/api/list`
|
||||
**Description:** List all available APIs
|
||||
|
||||
#### POST `/api/devtool/api/save`
|
||||
**Description:** Save API configuration
|
||||
**Parameters:**
|
||||
- `apiName` (string, required): API name
|
||||
- `endpoint` (string, required): API endpoint
|
||||
- `method` (string, required): HTTP method
|
||||
- `description` (string): API description
|
||||
- `parameters` (array): API parameters
|
||||
- `responses` (object): Response definitions
|
||||
|
||||
#### POST `/api/devtool/api/linter`
|
||||
**Description:** Lint API code for errors and best practices
|
||||
**Parameters:**
|
||||
- `code` (string, required): Code to lint
|
||||
- `language` (string, required): Programming language
|
||||
|
||||
#### POST `/api/devtool/api/prettier-format`
|
||||
**Description:** Format code using Prettier
|
||||
**Parameters:**
|
||||
- `code` (string, required): Code to format
|
||||
- `language` (string, required): Programming language
|
||||
|
||||
#### GET `/api/devtool/api/file-code`
|
||||
**Description:** Get source code of API file
|
||||
**Query Parameters:**
|
||||
- `file` (string, required): File path
|
||||
|
||||
### Content Management
|
||||
|
||||
#### Template Management
|
||||
|
||||
##### GET `/api/devtool/content/template/get-list`
|
||||
**Description:** Get list of available templates
|
||||
|
||||
##### GET `/api/devtool/content/template/list`
|
||||
**Description:** List all templates
|
||||
|
||||
##### POST `/api/devtool/content/template/import`
|
||||
**Description:** Import template
|
||||
**Parameters:**
|
||||
- `templateName` (string, required): Template name
|
||||
- `templateContent` (string, required): Template content
|
||||
- `templateType` (string, required): Template type
|
||||
|
||||
##### GET `/api/devtool/content/template/tag`
|
||||
**Description:** Get template tags
|
||||
|
||||
#### Code Management
|
||||
|
||||
##### GET `/api/devtool/content/code/file-code`
|
||||
**Description:** Get source code of file
|
||||
**Query Parameters:**
|
||||
- `file` (string, required): File path
|
||||
|
||||
##### POST `/api/devtool/content/code/save`
|
||||
**Description:** Save code to file
|
||||
**Parameters:**
|
||||
- `file` (string, required): File path
|
||||
- `content` (string, required): File content
|
||||
|
||||
##### POST `/api/devtool/content/code/linter`
|
||||
**Description:** Lint code for errors
|
||||
**Parameters:**
|
||||
- `code` (string, required): Code to lint
|
||||
- `language` (string, required): Programming language
|
||||
|
||||
##### POST `/api/devtool/content/code/prettier-format`
|
||||
**Description:** Format code using Prettier
|
||||
**Parameters:**
|
||||
- `code` (string, required): Code to format
|
||||
- `language` (string, required): Programming language
|
||||
|
||||
#### Canvas Management
|
||||
|
||||
##### GET `/api/devtool/content/canvas/file-code`
|
||||
**Description:** Get canvas file code
|
||||
**Query Parameters:**
|
||||
- `canvas` (string, required): Canvas identifier
|
||||
|
||||
### Lookup Data
|
||||
|
||||
#### GET `/api/devtool/lookup/list`
|
||||
**Description:** Get lookup data list
|
||||
|
||||
---
|
||||
|
||||
## Base URL Configuration
|
||||
|
||||
- **Development:** `http://localhost:3000`
|
||||
- **API Base Path:** `/api`
|
||||
|
||||
## Authentication
|
||||
|
||||
Most endpoints require authentication via Bearer token obtained from the `/api/auth/login` endpoint. The token should be included in the Authorization header:
|
||||
|
||||
```
|
||||
Authorization: Bearer <your-access-token>
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
All APIs follow a consistent response format:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 200,
|
||||
"message": "Success message",
|
||||
"data": {
|
||||
// Response data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
Error responses follow the same format with appropriate HTTP status codes:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 400,
|
||||
"message": "Error message",
|
||||
"errors": {
|
||||
// Validation errors if applicable
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Import Instructions
|
||||
|
||||
To import the Postman collection:
|
||||
|
||||
1. Open Postman
|
||||
2. Click "Import" button
|
||||
3. Select "Upload Files" tab
|
||||
4. Choose the `postman_collection.json` file
|
||||
5. Click "Import"
|
||||
|
||||
The collection includes:
|
||||
- Pre-configured environment variables
|
||||
- Automatic token management
|
||||
- Request examples with sample data
|
||||
- Organized folder structure for easy navigation
|
||||
|
||||
## Notes
|
||||
|
||||
- All development tool APIs are intended for development and administrative purposes
|
||||
- The API Platform provides proxy functionality for external API calls
|
||||
- Business logic APIs integrate with external services like OpenAI
|
||||
- Database operations through ORM tools should be used with caution in production environments
|
198
docs/IMPLEMENTATION_SUMMARY.md
Normal file
198
docs/IMPLEMENTATION_SUMMARY.md
Normal file
@ -0,0 +1,198 @@
|
||||
# Implementation Summary: Enhanced OpenAPI Configuration
|
||||
|
||||
This document summarizes the changes made to implement the requested OpenAPI configuration enhancements.
|
||||
|
||||
## Requirements Fulfilled
|
||||
|
||||
### 1. ✅ Change Dynamic and Static Routing to Markdown
|
||||
|
||||
**Original Request**: Change the dynamic and static routing to see the openapi.json to docs\openapi.md (rename the file too)
|
||||
|
||||
**Implementation**:
|
||||
- **Moved**: `public/openapi.json` → `docs/openapi.md`
|
||||
- **Updated**: `server/api/openapi.get.js` to serve markdown instead of JSON
|
||||
- **Modified**: `composables/useScalarConfig.js` to point to markdown file
|
||||
- **Enhanced**: Scalar viewer to parse JSON from markdown code blocks
|
||||
|
||||
**Files Changed**:
|
||||
- `docs/openapi.md` (new markdown file)
|
||||
- `server/api/openapi.get.js` (updated to serve markdown)
|
||||
- `composables/useScalarConfig.js` (updated default URL)
|
||||
- `pages/api-docs/index.vue` (enhanced JSON extraction from markdown)
|
||||
|
||||
### 2. ✅ Dynamic OpenAPI Generation from Collections
|
||||
|
||||
**Original Request**: Make a dynamic and static swagger json based on saved collection api in api platform
|
||||
|
||||
**Implementation**:
|
||||
- **Created**: `server/api/openapi/generate-from-collections.post.js` - API endpoint for generation
|
||||
- **Added**: Collection-to-OpenAPI conversion logic with schema inference
|
||||
- **Implemented**: Automatic tagging, parameter extraction, and response mapping
|
||||
- **Features**: Support for all HTTP methods, request bodies, query params, headers
|
||||
|
||||
**Generation Features**:
|
||||
- Collection names → API tags
|
||||
- Request names → Operation summaries
|
||||
- URL parsing → Path extraction
|
||||
- JSON bodies → Schema inference
|
||||
- Auth settings → Security configuration
|
||||
- Query params → OpenAPI parameters
|
||||
- Headers → OpenAPI parameters (excluding standard ones)
|
||||
|
||||
### 3. ✅ Enhanced Configuration Interface
|
||||
|
||||
**Original Request**: User can go to docs config, beside openapi json url, put button to pull and auto import the dynamic and static swagger json based on saved collection to code editor
|
||||
|
||||
**Implementation**:
|
||||
- **Enhanced**: `components/api-platform/ScalarConfigModal.vue` with three source options:
|
||||
1. **OpenAPI URL** - Traditional URL-based loading
|
||||
2. **Generate from Collections** - Auto-generation with one-click
|
||||
3. **Custom JSON** - Manual editing with validation
|
||||
|
||||
**Interface Features**:
|
||||
- Radio button selection for source type
|
||||
- Collections display with request counts
|
||||
- Generate button with collection validation
|
||||
- Code editor with JSON validation
|
||||
- Import/Export functionality
|
||||
- Real-time validation feedback
|
||||
|
||||
### 4. ✅ Code Editor with Validation
|
||||
|
||||
**Original Request**: The user can edit the file and save. To be safe, add option for open api url, or custom meaning the user just copy paste the json to code editor
|
||||
|
||||
**Implementation**:
|
||||
- **Added**: Built-in JSON editor with syntax highlighting
|
||||
- **Implemented**: Real-time validation with error reporting
|
||||
- **Created**: File import functionality (JSON/YAML support)
|
||||
- **Added**: Save functionality with server-side file management
|
||||
|
||||
**Editor Features**:
|
||||
- Live JSON validation
|
||||
- Error highlighting and reporting
|
||||
- File import/export
|
||||
- Auto-formatting
|
||||
- Save to server with filename validation
|
||||
|
||||
## New API Endpoints
|
||||
|
||||
### 1. Generate from Collections
|
||||
```
|
||||
POST /api/openapi/generate-from-collections
|
||||
```
|
||||
Converts saved API collections to OpenAPI 3.0.3 specification
|
||||
|
||||
### 2. Save OpenAPI Files
|
||||
```
|
||||
POST /api/docs/save-openapi
|
||||
```
|
||||
Saves generated/edited specifications to docs directory
|
||||
|
||||
### 3. Serve Documentation Files
|
||||
```
|
||||
GET /docs/[...slug]
|
||||
```
|
||||
Dynamic route for serving markdown and JSON files from docs directory
|
||||
|
||||
## File Structure Changes
|
||||
|
||||
```
|
||||
docs/
|
||||
├── openapi.md # Main OpenAPI specification (markdown)
|
||||
├── openapi-collections.md # Generated from collections
|
||||
├── openapi-custom.md # Custom edited specification
|
||||
└── OPENAPI_CONFIGURATION_GUIDE.md # Usage documentation
|
||||
|
||||
server/api/
|
||||
├── openapi.get.js # Serves markdown (updated)
|
||||
├── openapi/
|
||||
│ └── generate-from-collections.post.js # Collection generation
|
||||
├── docs/
|
||||
│ ├── save-openapi.post.js # File saving
|
||||
│ └── [...slug].get.js # File serving
|
||||
|
||||
components/api-platform/
|
||||
└── ScalarConfigModal.vue # Enhanced configuration UI
|
||||
|
||||
pages/api-docs/
|
||||
└── index.vue # Enhanced Scalar initialization
|
||||
```
|
||||
|
||||
## Technical Features
|
||||
|
||||
### Schema Inference Engine
|
||||
- Automatic type detection from JSON
|
||||
- Nested object/array handling
|
||||
- Required field detection
|
||||
- Example value preservation
|
||||
|
||||
### Validation System
|
||||
- Real-time JSON validation
|
||||
- OpenAPI structure validation
|
||||
- Error reporting with line numbers
|
||||
- Safe filename handling
|
||||
|
||||
### File Management
|
||||
- Server-side file operations
|
||||
- Dynamic file serving
|
||||
- Content-type detection
|
||||
- Cache headers for performance
|
||||
|
||||
### Security Measures
|
||||
- Path traversal prevention
|
||||
- Filename sanitization
|
||||
- Input validation
|
||||
- Error handling
|
||||
|
||||
## Usage Workflow
|
||||
|
||||
1. **Access Configuration**: Go to API Platform → Settings
|
||||
2. **Choose Source**: Select from URL, Collections, or Custom
|
||||
3. **Generate/Edit**: Use generation or manual editing
|
||||
4. **Validate**: Real-time validation feedback
|
||||
5. **Save**: Persist changes to server
|
||||
6. **Preview**: Test in documentation viewer
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
- Existing URL-based configurations continue to work
|
||||
- Default behavior preserved for existing users
|
||||
- Graceful fallbacks for missing collections
|
||||
- Error handling for invalid configurations
|
||||
|
||||
## Benefits
|
||||
|
||||
### For Developers
|
||||
- Automated documentation generation
|
||||
- Reduced manual maintenance
|
||||
- Real-time validation feedback
|
||||
- Multiple input methods
|
||||
|
||||
### For API Consumers
|
||||
- Always up-to-date documentation
|
||||
- Consistent specification format
|
||||
- Multiple access methods
|
||||
- Rich interactive interface
|
||||
|
||||
### For Organizations
|
||||
- Centralized documentation management
|
||||
- Automated workflow integration
|
||||
- Version control friendly
|
||||
- Standard compliance (OpenAPI 3.0.3)
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Planned Features
|
||||
- YAML support in editor
|
||||
- Version history tracking
|
||||
- Collaborative editing
|
||||
- Custom schema templates
|
||||
- Automated testing integration
|
||||
|
||||
### Integration Opportunities
|
||||
- CI/CD pipeline integration
|
||||
- Git hooks for auto-generation
|
||||
- API gateway synchronization
|
||||
- Monitoring and analytics
|
||||
|
||||
This implementation provides a comprehensive solution for OpenAPI specification management, balancing automation with customization while maintaining ease of use and standard compliance.
|
250
docs/OPENAPI_CONFIGURATION_GUIDE.md
Normal file
250
docs/OPENAPI_CONFIGURATION_GUIDE.md
Normal file
@ -0,0 +1,250 @@
|
||||
# OpenAPI Configuration Guide
|
||||
|
||||
This guide explains how to configure and use the enhanced OpenAPI documentation features in the Corrad AF 2024 API Platform.
|
||||
|
||||
## Overview
|
||||
|
||||
The platform now supports three methods for providing OpenAPI specifications:
|
||||
|
||||
1. **OpenAPI URL** - Traditional URL-based specification loading
|
||||
2. **Generate from Collections** - Auto-generate from saved API collections
|
||||
3. **Custom JSON** - Manual specification editing with validation
|
||||
|
||||
## Access Configuration
|
||||
|
||||
Navigate to your API platform and open the **Scalar Configuration Modal** to access these features:
|
||||
|
||||
1. Go to `/api-platform`
|
||||
2. Look for the "API Documentation Settings" or configuration icon
|
||||
3. Click on the **Basic Info** tab
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### 1. OpenAPI URL (Default)
|
||||
|
||||
Use this option when you have an existing OpenAPI specification available via URL.
|
||||
|
||||
**Features:**
|
||||
- Support for both JSON and Markdown files
|
||||
- Dynamic server URL detection
|
||||
- Proxy support for CORS issues
|
||||
|
||||
**Usage:**
|
||||
1. Select "OpenAPI URL" radio button
|
||||
2. Enter your specification URL in the "OpenAPI Specification URL" field
|
||||
3. Optionally configure a proxy URL if needed
|
||||
|
||||
### 2. Generate from Collections
|
||||
|
||||
Automatically generate OpenAPI specifications from your saved API collections.
|
||||
|
||||
**Features:**
|
||||
- Auto-detection of endpoints from collections
|
||||
- Schema inference from request/response bodies
|
||||
- Automatic tagging by collection names
|
||||
- Parameter extraction from queries and headers
|
||||
|
||||
**Usage:**
|
||||
1. Select "Generate from Collections" radio button
|
||||
2. Review available collections displayed
|
||||
3. Click "Generate" button
|
||||
4. The system will:
|
||||
- Create an OpenAPI 3.0.3 specification
|
||||
- Generate markdown documentation
|
||||
- Save to `/docs/openapi-collections.md`
|
||||
- Switch to custom editor for review
|
||||
|
||||
**Generated Features:**
|
||||
- Collection names become API tags
|
||||
- Request names become operation summaries
|
||||
- Query parameters and headers become OpenAPI parameters
|
||||
- Request bodies are analyzed for schema generation
|
||||
- Basic response schemas (success/error) are included
|
||||
|
||||
### 3. Custom JSON
|
||||
|
||||
Manually create or edit OpenAPI specifications with real-time validation.
|
||||
|
||||
**Features:**
|
||||
- Live JSON validation
|
||||
- File import support (JSON/YAML)
|
||||
- Syntax highlighting
|
||||
- Error reporting
|
||||
- Auto-save functionality
|
||||
|
||||
**Usage:**
|
||||
1. Select "Custom JSON" radio button
|
||||
2. Either:
|
||||
- Paste JSON directly into the editor
|
||||
- Click "Import" to upload a file
|
||||
- Use "Generate" from collections first, then edit
|
||||
3. The editor provides:
|
||||
- Real-time validation feedback
|
||||
- Error highlighting
|
||||
- Valid JSON confirmation
|
||||
4. Click "Save Custom JSON" to save changes
|
||||
|
||||
## File Management
|
||||
|
||||
### Generated Files
|
||||
|
||||
When using collection generation or custom JSON, files are saved to:
|
||||
|
||||
- **Collections**: `/docs/openapi-collections.md`
|
||||
- **Custom**: `/docs/openapi-custom.md`
|
||||
- **Original**: `/docs/openapi.md`
|
||||
|
||||
### File Access
|
||||
|
||||
All generated files are accessible via:
|
||||
|
||||
- **Web Interface**: `/api-docs` (interactive documentation)
|
||||
- **Direct Access**: `/docs/[filename]` (raw file content)
|
||||
- **API Endpoint**: `/api/openapi` (dynamic markdown)
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Generate from Collections
|
||||
```
|
||||
POST /api/openapi/generate-from-collections
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"collections": [/* array of collections */],
|
||||
"config": {
|
||||
"title": "API Title",
|
||||
"description": "API Description",
|
||||
"version": "1.0.0",
|
||||
"contact": {
|
||||
"name": "Support",
|
||||
"email": "support@example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Save OpenAPI File
|
||||
```
|
||||
POST /api/docs/save-openapi
|
||||
```
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"content": "file content",
|
||||
"filename": "openapi.md"
|
||||
}
|
||||
```
|
||||
|
||||
### Serve Documentation Files
|
||||
```
|
||||
GET /docs/[filename]
|
||||
```
|
||||
|
||||
## Schema Generation
|
||||
|
||||
When generating from collections, the system automatically:
|
||||
|
||||
### 1. Infers Request Schemas
|
||||
- Parses JSON request bodies
|
||||
- Creates type-appropriate schemas
|
||||
- Handles nested objects and arrays
|
||||
- Provides example values
|
||||
|
||||
### 2. Creates Parameters
|
||||
- Query parameters from URL params
|
||||
- Header parameters (excluding standard headers)
|
||||
- Path parameters from URL structure
|
||||
|
||||
### 3. Generates Responses
|
||||
- Standard success/error response schemas
|
||||
- Content-type detection
|
||||
- Status code mapping
|
||||
|
||||
### 4. Security Configuration
|
||||
- Automatic bearer token detection
|
||||
- Auth method mapping from collection settings
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Collection Organization
|
||||
1. **Naming**: Use descriptive collection names (become API tags)
|
||||
2. **Grouping**: Group related endpoints in same collection
|
||||
3. **Documentation**: Add descriptions to requests for better summaries
|
||||
|
||||
### Request Configuration
|
||||
1. **URLs**: Use full URLs with proper base paths
|
||||
2. **Parameters**: Configure active parameters with descriptions
|
||||
3. **Bodies**: Use valid JSON for better schema inference
|
||||
4. **Headers**: Include necessary headers, exclude standard ones
|
||||
|
||||
### Custom Editing
|
||||
1. **Validation**: Always validate before saving
|
||||
2. **Backup**: Export configurations before major changes
|
||||
3. **Testing**: Use preview mode to test changes
|
||||
4. **Structure**: Follow OpenAPI 3.0.3 specification
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Generation Fails**
|
||||
- Ensure collections contain valid requests
|
||||
- Check request URLs are properly formatted
|
||||
- Verify JSON bodies are valid
|
||||
|
||||
**Validation Errors**
|
||||
- Missing required OpenAPI fields (`openapi`, `info`)
|
||||
- Invalid JSON syntax
|
||||
- Incorrect schema structure
|
||||
|
||||
**Display Issues**
|
||||
- Check markdown JSON extraction
|
||||
- Verify Scalar initialization
|
||||
- Review browser console for errors
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable debug logging by checking browser console when:
|
||||
- Configuration loading fails
|
||||
- Generation produces unexpected results
|
||||
- Scalar fails to initialize
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From Static OpenAPI
|
||||
1. Access configuration modal
|
||||
2. Select "Custom JSON"
|
||||
3. Import existing specification
|
||||
4. Save as new file
|
||||
5. Update configuration
|
||||
|
||||
### From External URLs
|
||||
1. Download current specification
|
||||
2. Use "Custom JSON" import feature
|
||||
3. Edit as needed
|
||||
4. Save locally for better control
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Postman Integration
|
||||
1. Generate or configure OpenAPI spec
|
||||
2. Access via `/docs/openapi-collections.md`
|
||||
3. Extract JSON from markdown
|
||||
4. Import into Postman
|
||||
|
||||
### Insomnia Integration
|
||||
1. Use same process as Postman
|
||||
2. Import OpenAPI JSON directly
|
||||
3. All endpoints and schemas preserved
|
||||
|
||||
### Development Workflow
|
||||
1. Create API collections during development
|
||||
2. Generate OpenAPI specification automatically
|
||||
3. Review and edit as needed
|
||||
4. Deploy documentation
|
||||
5. Keep collections updated for regeneration
|
||||
|
||||
This enhanced system provides flexibility while maintaining automation, allowing both rapid prototyping and detailed customization of API documentation.
|
191
docs/PHASE-3-IMPLEMENTATION-SUMMARY.md
Normal file
191
docs/PHASE-3-IMPLEMENTATION-SUMMARY.md
Normal file
@ -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!**
|
445
docs/openapi-custom.json
Normal file
445
docs/openapi-custom.json
Normal file
@ -0,0 +1,445 @@
|
||||
{
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "Corrad AF 2024 API Platform",
|
||||
"description": "Complete API reference for the Corrad AF 2024 API Platform project including authentication, business logic, development tools, and platform management endpoints.",
|
||||
"version": "2.0.0",
|
||||
"contact": {
|
||||
"name": "API Support",
|
||||
"email": "support@corradaf.com",
|
||||
"url": "https://corradaf.com"
|
||||
},
|
||||
"license": {
|
||||
"name": "MIT",
|
||||
"url": "https://opensource.org/licenses/MIT"
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "{protocol}://{host}:{port}/api",
|
||||
"description": "API Server",
|
||||
"variables": {
|
||||
"protocol": {
|
||||
"enum": ["http", "https"],
|
||||
"default": "http"
|
||||
},
|
||||
"host": {
|
||||
"default": "localhost"
|
||||
},
|
||||
"port": {
|
||||
"default": "3000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
{
|
||||
"name": "authentication",
|
||||
"description": "Authentication and authorization operations"
|
||||
},
|
||||
{
|
||||
"name": "business-logic",
|
||||
"description": "Core business functionality"
|
||||
},
|
||||
{
|
||||
"name": "api-platform",
|
||||
"description": "API platform and proxy operations"
|
||||
},
|
||||
{
|
||||
"name": "metabase",
|
||||
"description": "Analytics and reporting integration"
|
||||
},
|
||||
{
|
||||
"name": "devtool-users",
|
||||
"description": "User management for development"
|
||||
},
|
||||
{
|
||||
"name": "devtool-roles",
|
||||
"description": "Role management for development"
|
||||
},
|
||||
{
|
||||
"name": "devtool-menu",
|
||||
"description": "Menu management for development"
|
||||
},
|
||||
{
|
||||
"name": "devtool-orm",
|
||||
"description": "Database and ORM management"
|
||||
},
|
||||
{
|
||||
"name": "devtool-config",
|
||||
"description": "Configuration management"
|
||||
},
|
||||
{
|
||||
"name": "devtool-api",
|
||||
"description": "API development tools"
|
||||
},
|
||||
{
|
||||
"name": "devtool-content",
|
||||
"description": "Content management tools"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/auth/login": {
|
||||
"post": {
|
||||
"tags": ["authentication"],
|
||||
"summary": "User Login",
|
||||
"description": "Authenticate user and receive access/refresh tokens",
|
||||
"operationId": "login",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"description": "User's username"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"description": "User's password"
|
||||
}
|
||||
},
|
||||
"required": ["username", "password"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Login successful",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - missing username or password",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Invalid credentials",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/logout": {
|
||||
"get": {
|
||||
"tags": ["authentication"],
|
||||
"summary": "User Logout",
|
||||
"description": "Logout user and clear authentication cookies",
|
||||
"operationId": "logout",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Logout successful",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SuccessResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/validate": {
|
||||
"get": {
|
||||
"tags": ["authentication"],
|
||||
"summary": "Validate Token",
|
||||
"description": "Validate current authentication token",
|
||||
"operationId": "validateToken",
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Token is valid",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SuccessResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Invalid or expired token",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/analyze-asnaf": {
|
||||
"post": {
|
||||
"tags": ["business-logic"],
|
||||
"summary": "Analyze Asnaf Profile",
|
||||
"description": "Analyze Asnaf profile using AI/OpenAI integration to determine eligibility and assistance recommendations",
|
||||
"operationId": "analyzeAsnaf",
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AsnafAnalysisRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Analysis completed successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AsnafAnalysisResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request data",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Analysis failed",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"SuccessResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"statusCode": {
|
||||
"type": "integer",
|
||||
"example": 200
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Operation successful"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"ErrorResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"statusCode": {
|
||||
"type": "integer",
|
||||
"example": 400
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Error message"
|
||||
},
|
||||
"errors": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoginResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"statusCode": {
|
||||
"type": "integer",
|
||||
"example": 200
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Login success"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"example": "user@example.com"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": ["admin", "user"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AsnafAnalysisRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"monthlyIncome": {
|
||||
"type": "string",
|
||||
"description": "Monthly income amount",
|
||||
"example": "2000"
|
||||
},
|
||||
"otherIncome": {
|
||||
"type": "string",
|
||||
"description": "Other income sources",
|
||||
"example": "500"
|
||||
},
|
||||
"totalIncome": {
|
||||
"type": "string",
|
||||
"description": "Total income amount",
|
||||
"example": "2500"
|
||||
},
|
||||
"occupation": {
|
||||
"type": "string",
|
||||
"description": "Applicant's occupation",
|
||||
"example": "Clerk"
|
||||
},
|
||||
"maritalStatus": {
|
||||
"type": "string",
|
||||
"description": "Marital status",
|
||||
"example": "Married"
|
||||
},
|
||||
"dependents": {
|
||||
"type": "array",
|
||||
"description": "List of dependents",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"age": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["monthlyIncome", "totalIncome", "occupation", "maritalStatus"]
|
||||
},
|
||||
"AsnafAnalysisResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hadKifayahPercentage": {
|
||||
"type": "string",
|
||||
"example": "75%"
|
||||
},
|
||||
"kategoriAsnaf": {
|
||||
"type": "string",
|
||||
"example": "Miskin"
|
||||
},
|
||||
"kategoriKeluarga": {
|
||||
"type": "string",
|
||||
"example": "Miskin (50-100% HK)"
|
||||
},
|
||||
"cadanganKategori": {
|
||||
"type": "string",
|
||||
"example": "Miskin"
|
||||
},
|
||||
"statusKelayakan": {
|
||||
"type": "string",
|
||||
"example": "Layak (Miskin)"
|
||||
},
|
||||
"cadanganBantuan": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nama": {
|
||||
"type": "string",
|
||||
"example": "Bantuan Kewangan Bulanan"
|
||||
},
|
||||
"peratusan": {
|
||||
"type": "string",
|
||||
"example": "90%"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ramalanJangkaMasaPulih": {
|
||||
"type": "string",
|
||||
"example": "6 bulan"
|
||||
},
|
||||
"rumusan": {
|
||||
"type": "string",
|
||||
"example": "Pemohon memerlukan perhatian segera."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securitySchemes": {
|
||||
"bearerAuth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT",
|
||||
"description": "JWT Bearer token authentication"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
445
docs/openapi.json
Normal file
445
docs/openapi.json
Normal file
@ -0,0 +1,445 @@
|
||||
{
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "Corrad AF 2024 API Platform",
|
||||
"description": "Complete API reference for the Corrad AF 2024 API Platform project including authentication, business logic, development tools, and platform management endpoints.",
|
||||
"version": "2.0.0",
|
||||
"contact": {
|
||||
"name": "API Support",
|
||||
"email": "support@corradaf.com",
|
||||
"url": "https://corradaf.com"
|
||||
},
|
||||
"license": {
|
||||
"name": "MIT",
|
||||
"url": "https://opensource.org/licenses/MIT"
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "{protocol}://{host}:{port}/api",
|
||||
"description": "API Server",
|
||||
"variables": {
|
||||
"protocol": {
|
||||
"enum": ["http", "https"],
|
||||
"default": "http"
|
||||
},
|
||||
"host": {
|
||||
"default": "localhost"
|
||||
},
|
||||
"port": {
|
||||
"default": "3000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
{
|
||||
"name": "authentication",
|
||||
"description": "Authentication and authorization operations"
|
||||
},
|
||||
{
|
||||
"name": "business-logic",
|
||||
"description": "Core business functionality"
|
||||
},
|
||||
{
|
||||
"name": "api-platform",
|
||||
"description": "API platform and proxy operations"
|
||||
},
|
||||
{
|
||||
"name": "metabase",
|
||||
"description": "Analytics and reporting integration"
|
||||
},
|
||||
{
|
||||
"name": "devtool-users",
|
||||
"description": "User management for development"
|
||||
},
|
||||
{
|
||||
"name": "devtool-roles",
|
||||
"description": "Role management for development"
|
||||
},
|
||||
{
|
||||
"name": "devtool-menu",
|
||||
"description": "Menu management for development"
|
||||
},
|
||||
{
|
||||
"name": "devtool-orm",
|
||||
"description": "Database and ORM management"
|
||||
},
|
||||
{
|
||||
"name": "devtool-config",
|
||||
"description": "Configuration management"
|
||||
},
|
||||
{
|
||||
"name": "devtool-api",
|
||||
"description": "API development tools"
|
||||
},
|
||||
{
|
||||
"name": "devtool-content",
|
||||
"description": "Content management tools"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/auth/login": {
|
||||
"post": {
|
||||
"tags": ["authentication"],
|
||||
"summary": "User Login",
|
||||
"description": "Authenticate user and receive access/refresh tokens",
|
||||
"operationId": "login",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"description": "User's username"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"format": "password",
|
||||
"description": "User's password"
|
||||
}
|
||||
},
|
||||
"required": ["username", "password"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Login successful",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request - missing username or password",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Invalid credentials",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "User not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/logout": {
|
||||
"get": {
|
||||
"tags": ["authentication"],
|
||||
"summary": "User Logout",
|
||||
"description": "Logout user and clear authentication cookies",
|
||||
"operationId": "logout",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Logout successful",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SuccessResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/validate": {
|
||||
"get": {
|
||||
"tags": ["authentication"],
|
||||
"summary": "Validate Token",
|
||||
"description": "Validate current authentication token",
|
||||
"operationId": "validateToken",
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Token is valid",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SuccessResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Invalid or expired token",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/analyze-asnaf": {
|
||||
"post": {
|
||||
"tags": ["business-logic"],
|
||||
"summary": "Analyze Asnaf Profile",
|
||||
"description": "Analyze Asnaf profile using AI/OpenAI integration to determine eligibility and assistance recommendations",
|
||||
"operationId": "analyzeAsnaf",
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AsnafAnalysisRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Analysis completed successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AsnafAnalysisResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid request data",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Analysis failed",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"SuccessResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"statusCode": {
|
||||
"type": "integer",
|
||||
"example": 200
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Operation successful"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"ErrorResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"statusCode": {
|
||||
"type": "integer",
|
||||
"example": 400
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Error message"
|
||||
},
|
||||
"errors": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoginResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"statusCode": {
|
||||
"type": "integer",
|
||||
"example": 200
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "Login success"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"example": "user@example.com"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": ["admin", "user"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AsnafAnalysisRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"monthlyIncome": {
|
||||
"type": "string",
|
||||
"description": "Monthly income amount",
|
||||
"example": "2000"
|
||||
},
|
||||
"otherIncome": {
|
||||
"type": "string",
|
||||
"description": "Other income sources",
|
||||
"example": "500"
|
||||
},
|
||||
"totalIncome": {
|
||||
"type": "string",
|
||||
"description": "Total income amount",
|
||||
"example": "2500"
|
||||
},
|
||||
"occupation": {
|
||||
"type": "string",
|
||||
"description": "Applicant's occupation",
|
||||
"example": "Clerk"
|
||||
},
|
||||
"maritalStatus": {
|
||||
"type": "string",
|
||||
"description": "Marital status",
|
||||
"example": "Married"
|
||||
},
|
||||
"dependents": {
|
||||
"type": "array",
|
||||
"description": "List of dependents",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"age": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["monthlyIncome", "totalIncome", "occupation", "maritalStatus"]
|
||||
},
|
||||
"AsnafAnalysisResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hadKifayahPercentage": {
|
||||
"type": "string",
|
||||
"example": "75%"
|
||||
},
|
||||
"kategoriAsnaf": {
|
||||
"type": "string",
|
||||
"example": "Miskin"
|
||||
},
|
||||
"kategoriKeluarga": {
|
||||
"type": "string",
|
||||
"example": "Miskin (50-100% HK)"
|
||||
},
|
||||
"cadanganKategori": {
|
||||
"type": "string",
|
||||
"example": "Miskin"
|
||||
},
|
||||
"statusKelayakan": {
|
||||
"type": "string",
|
||||
"example": "Layak (Miskin)"
|
||||
},
|
||||
"cadanganBantuan": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nama": {
|
||||
"type": "string",
|
||||
"example": "Bantuan Kewangan Bulanan"
|
||||
},
|
||||
"peratusan": {
|
||||
"type": "string",
|
||||
"example": "90%"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ramalanJangkaMasaPulih": {
|
||||
"type": "string",
|
||||
"example": "6 bulan"
|
||||
},
|
||||
"rumusan": {
|
||||
"type": "string",
|
||||
"example": "Pemohon memerlukan perhatian segera."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securitySchemes": {
|
||||
"bearerAuth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT",
|
||||
"description": "JWT Bearer token authentication"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
1151
docs/postman_collection.json
Normal file
1151
docs/postman_collection.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -91,4 +91,4 @@ export default [
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
1381
pages/api-docs/index.vue
Normal file
1381
pages/api-docs/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,7 @@ import SaveRequestModal from '~/components/api-platform/SaveRequestModal.vue'
|
||||
import CodeGenerationModal from '~/components/api-platform/CodeGenerationModal.vue'
|
||||
import ImportExportModal from '~/components/api-platform/ImportExportModal.vue'
|
||||
import TestScriptsModal from '~/components/api-platform/TestScriptsModal.vue'
|
||||
import ScalarConfigModal from '~/components/api-platform/ScalarConfigModal.vue'
|
||||
|
||||
definePageMeta({
|
||||
title: "API Platform",
|
||||
@ -26,6 +27,7 @@ const {
|
||||
const showCodeGenerationModal = ref(false)
|
||||
const showImportExportModal = ref(false)
|
||||
const showTestScriptsModal = ref(false)
|
||||
const showScalarConfigModal = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -100,6 +102,37 @@ const showTestScriptsModal = ref(false)
|
||||
<Icon name="ic:outline-save" size="16" />
|
||||
Save
|
||||
</rs-button>
|
||||
|
||||
<!-- Separator -->
|
||||
<div class="h-6 w-px bg-gray-300 dark:bg-gray-600"></div>
|
||||
|
||||
<!-- Scalar Config Button -->
|
||||
<rs-button
|
||||
variant="secondary-outline"
|
||||
size="sm"
|
||||
@click="showScalarConfigModal = true"
|
||||
class="flex items-center gap-2"
|
||||
title="Configure API Documentation"
|
||||
>
|
||||
<Icon name="ic:outline-settings" size="16" />
|
||||
Docs Config
|
||||
</rs-button>
|
||||
|
||||
<!-- API Docs Button -->
|
||||
<nuxt-link
|
||||
to="/api-docs"
|
||||
target="_blank"
|
||||
class="inline-flex"
|
||||
>
|
||||
<rs-button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<Icon name="ic:outline-api" size="16" />
|
||||
API Docs
|
||||
</rs-button>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -160,6 +193,13 @@ const showTestScriptsModal = ref(false)
|
||||
@close="showTestScriptsModal = false"
|
||||
/>
|
||||
|
||||
<!-- Scalar Configuration Modal -->
|
||||
<ScalarConfigModal
|
||||
v-if="showScalarConfigModal"
|
||||
@close="showScalarConfigModal = false"
|
||||
@saved="onScalarConfigSaved"
|
||||
/>
|
||||
|
||||
<!-- Enhanced Notification System -->
|
||||
<div class="fixed top-4 right-4 z-50 space-y-3 max-w-sm">
|
||||
<TransitionGroup
|
||||
@ -204,4 +244,12 @@ const showTestScriptsModal = ref(false)
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Handle Scalar configuration saved event
|
||||
const onScalarConfigSaved = () => {
|
||||
// You could refresh the API docs tab if it's open, or show additional feedback
|
||||
console.log('Scalar configuration has been updated')
|
||||
}
|
||||
</script>
|
@ -11,328 +11,305 @@ definePageMeta({
|
||||
],
|
||||
});
|
||||
|
||||
// Data baru untuk lapangan terbang teratas
|
||||
const topAirports = ref([
|
||||
{
|
||||
rank: 1,
|
||||
name: "Lapangan Terbang Antarabangsa Kuala Lumpur (KLIA)",
|
||||
visitors: 62000000,
|
||||
},
|
||||
{
|
||||
rank: 2,
|
||||
name: "Lapangan Terbang Antarabangsa Kota Kinabalu",
|
||||
visitors: 9000000,
|
||||
},
|
||||
{ rank: 3, name: "Lapangan Terbang Antarabangsa Penang", visitors: 8000000 },
|
||||
{ rank: 4, name: "Lapangan Terbang Antarabangsa Kuching", visitors: 5500000 },
|
||||
{
|
||||
rank: 5,
|
||||
name: "Lapangan Terbang Antarabangsa Langkawi",
|
||||
visitors: 3000000,
|
||||
},
|
||||
]);
|
||||
|
||||
// Data baru untuk kad ringkasan pantas
|
||||
const quickSummary = ref([
|
||||
{ title: "Jumlah Pelawat", value: "10.5 Juta", icon: "ic:outline-people" },
|
||||
{
|
||||
title: "Pendapatan Pelancongan",
|
||||
value: "RM 86.14 Bilion",
|
||||
icon: "ic:outline-attach-money",
|
||||
},
|
||||
{
|
||||
title: "Tempoh Penginapan Purata",
|
||||
value: "6.1 Hari",
|
||||
icon: "ic:outline-hotel",
|
||||
},
|
||||
{
|
||||
title: "Kepuasan Pelancong",
|
||||
value: "92%",
|
||||
icon: "ic:outline-sentiment-satisfied",
|
||||
},
|
||||
]);
|
||||
|
||||
// Data Pelawat Malaysia
|
||||
const visitorData = ref([
|
||||
{
|
||||
name: "Pelawat Tempatan",
|
||||
data: [5000000, 5500000, 6000000, 6500000, 7000000, 7500000],
|
||||
},
|
||||
{
|
||||
name: "Pelawat Asing",
|
||||
data: [3000000, 3500000, 4000000, 4500000, 5000000, 5500000],
|
||||
},
|
||||
]);
|
||||
|
||||
// Data Pelawat Asing mengikut Negeri
|
||||
const foreignVisitorsByState = ref([
|
||||
{ state: "Selangor", visitors: 1500000 },
|
||||
{ state: "Pulau Pinang", visitors: 1200000 },
|
||||
{ state: "Johor", visitors: 1000000 },
|
||||
{ state: "Sabah", visitors: 800000 },
|
||||
{ state: "Sarawak", visitors: 600000 },
|
||||
{ state: "Melaka", visitors: 500000 },
|
||||
{ state: "Kedah", visitors: 400000 },
|
||||
{ state: "Negeri Sembilan", visitors: 300000 },
|
||||
{ state: "Perak", visitors: 250000 },
|
||||
{ state: "Terengganu", visitors: 200000 },
|
||||
{ state: "Kelantan", visitors: 150000 },
|
||||
{ state: "Pahang", visitors: 100000 },
|
||||
{ state: "Perlis", visitors: 50000 },
|
||||
]);
|
||||
|
||||
// Lapangan Terbang Keberangkatan Teratas
|
||||
const departureData = ref([
|
||||
{ airport: "JFK", departures: 1500 },
|
||||
{ airport: "LHR", departures: 1200 },
|
||||
{ airport: "CDG", departures: 1000 },
|
||||
{ airport: "DXB", departures: 800 },
|
||||
{ airport: "SIN", departures: 600 },
|
||||
]);
|
||||
|
||||
// Data Pelancong Berulang
|
||||
const repeatVisitorsData = ref([
|
||||
{ category: "1-2 kali", percentage: 45 },
|
||||
{ category: "3-5 kali", percentage: 30 },
|
||||
{ category: "6-10 kali", percentage: 15 },
|
||||
{ category: ">10 kali", percentage: 10 },
|
||||
]);
|
||||
|
||||
// Data Negara Asal Pelancong Asing Teratas
|
||||
const topVisitorCountries = ref([
|
||||
{ country: "Singapura", visitors: 1500000 },
|
||||
{ country: "Indonesia", visitors: 1200000 },
|
||||
{ country: "China", visitors: 1000000 },
|
||||
{ country: "Thailand", visitors: 800000 },
|
||||
{ country: "India", visitors: 600000 },
|
||||
]);
|
||||
|
||||
const chartOptionsVisitors = computed(() => ({
|
||||
chart: { height: 350, type: "line" },
|
||||
stroke: { curve: "smooth", width: 2 },
|
||||
xaxis: { categories: ["2018", "2019", "2020", "2021", "2022", "2023"] },
|
||||
yaxis: { title: { text: "Bilangan Pelawat" } },
|
||||
}));
|
||||
|
||||
const chartOptionsForeignVisitors = computed(() => ({
|
||||
chart: { type: "bar" },
|
||||
plotOptions: { bar: { horizontal: true } },
|
||||
xaxis: { categories: foreignVisitorsByState.value.map((item) => item.state) },
|
||||
}));
|
||||
|
||||
const chartOptionsDeparture = computed(() => ({
|
||||
chart: { type: "bar" },
|
||||
plotOptions: { bar: { horizontal: true } },
|
||||
xaxis: { categories: departureData.value.map((item) => item.airport) },
|
||||
}));
|
||||
|
||||
const chartOptionsRepeatVisitors = computed(() => ({
|
||||
chart: { type: "pie" },
|
||||
labels: repeatVisitorsData.value.map((item) => item.category),
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 480,
|
||||
options: {
|
||||
chart: {
|
||||
width: 200,
|
||||
},
|
||||
legend: {
|
||||
position: "bottom",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
const chartOptionsTopCountries = computed(() => ({
|
||||
chart: { type: "bar" },
|
||||
plotOptions: {
|
||||
bar: { horizontal: false, columnWidth: "55%", endingShape: "rounded" },
|
||||
},
|
||||
dataLabels: { enabled: false },
|
||||
stroke: { show: true, width: 2, colors: ["transparent"] },
|
||||
xaxis: { categories: topVisitorCountries.value.map((item) => item.country) },
|
||||
yaxis: { title: { text: "Bilangan Pelawat" } },
|
||||
fill: { opacity: 1 },
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function (val) {
|
||||
return val.toLocaleString() + " pelawat";
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
onMounted(() => {
|
||||
// Sebarang logik yang diperlukan semasa pemasangan
|
||||
// Framework information
|
||||
const frameworkInfo = ref({
|
||||
name: "corradAF",
|
||||
version: "1.0.0",
|
||||
description: "Corrad Application Framework - A comprehensive Nuxt.js template for rapid application development",
|
||||
features: [
|
||||
"Authentication System",
|
||||
"User Management",
|
||||
"Role-based Access Control",
|
||||
"Development Tools Suite",
|
||||
"API Management",
|
||||
"Menu Configuration",
|
||||
"Content Management",
|
||||
"Code Playground",
|
||||
"ORM Integration",
|
||||
"Responsive Design"
|
||||
]
|
||||
});
|
||||
|
||||
// Development tools available
|
||||
const devTools = ref([
|
||||
{
|
||||
title: "User Management",
|
||||
description: "Manage users and roles with comprehensive CRUD operations",
|
||||
icon: "mdi:account-group",
|
||||
path: "/devtool/user-management/user",
|
||||
color: "blue"
|
||||
},
|
||||
{
|
||||
title: "Menu Editor",
|
||||
description: "Configure navigation menus and application structure",
|
||||
icon: "mdi:menu",
|
||||
path: "/devtool/menu-editor",
|
||||
color: "green"
|
||||
},
|
||||
{
|
||||
title: "API Editor",
|
||||
description: "Design and test API endpoints with interactive tools",
|
||||
icon: "mdi:api",
|
||||
path: "/devtool/api-editor",
|
||||
color: "purple"
|
||||
},
|
||||
{
|
||||
title: "Content Editor",
|
||||
description: "Manage dynamic content and templates",
|
||||
icon: "mdi:file-document-edit",
|
||||
path: "/devtool/content-editor",
|
||||
color: "orange"
|
||||
},
|
||||
{
|
||||
title: "Code Playground",
|
||||
description: "Test and prototype code snippets in real-time",
|
||||
icon: "mdi:code-braces",
|
||||
path: "/devtool/code-playground",
|
||||
color: "indigo"
|
||||
},
|
||||
{
|
||||
title: "ORM Tools",
|
||||
description: "Database schema management and query tools",
|
||||
icon: "mdi:database",
|
||||
path: "/devtool/orm",
|
||||
color: "red"
|
||||
},
|
||||
{
|
||||
title: "Configuration",
|
||||
description: "System settings and environment configuration",
|
||||
icon: "mdi:cog",
|
||||
path: "/devtool/config",
|
||||
color: "gray"
|
||||
}
|
||||
]);
|
||||
|
||||
// Quick stats
|
||||
const quickStats = ref([
|
||||
{ title: "Dev Tools", value: "7", icon: "mdi:tools" },
|
||||
{ title: "Components", value: "50+", icon: "mdi:view-grid" },
|
||||
{ title: "Auth System", value: "Ready", icon: "mdi:shield-check" },
|
||||
{ title: "Framework", value: "Nuxt 3", icon: "mdi:nuxt" }
|
||||
]);
|
||||
|
||||
// Getting started steps
|
||||
const gettingStarted = ref([
|
||||
{
|
||||
step: 1,
|
||||
title: "Clone Repository",
|
||||
description: "Clone this template to start your new project",
|
||||
command: "git clone <repository-url> your-project-name"
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: "Install Dependencies",
|
||||
description: "Install all required packages",
|
||||
command: "yarn install"
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: "Configure Environment",
|
||||
description: "Set up your environment variables and database",
|
||||
command: "cp .env.example .env"
|
||||
},
|
||||
{
|
||||
step: 4,
|
||||
title: "Start Development",
|
||||
description: "Run the development server",
|
||||
command: "yarn dev"
|
||||
}
|
||||
]);
|
||||
|
||||
function navigateToTool(path) {
|
||||
navigateTo(path);
|
||||
}
|
||||
|
||||
function getColorClasses(color) {
|
||||
const colorMap = {
|
||||
blue: 'bg-blue-100 text-blue-600 hover:bg-blue-200',
|
||||
green: 'bg-green-100 text-green-600 hover:bg-green-200',
|
||||
purple: 'bg-purple-100 text-purple-600 hover:bg-purple-200',
|
||||
orange: 'bg-orange-100 text-orange-600 hover:bg-orange-200',
|
||||
indigo: 'bg-indigo-100 text-indigo-600 hover:bg-indigo-200',
|
||||
red: 'bg-red-100 text-red-600 hover:bg-red-200',
|
||||
gray: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
};
|
||||
return colorMap[color] || 'bg-gray-100 text-gray-600 hover:bg-gray-200';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="space-y-8">
|
||||
<LayoutsBreadcrumb />
|
||||
<!-- Kad Ringkasan Pantas -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-4 gap-6">
|
||||
|
||||
<!-- Welcome Header -->
|
||||
<div class="text-center py-12 bg-gradient-to-br from-primary/10 to-secondary/10 rounded-2xl">
|
||||
<div class="max-w-4xl mx-auto px-6">
|
||||
<h1 class="text-4xl md:text-6xl font-bold text-primary mb-4">
|
||||
Welcome to {{ frameworkInfo.name }}
|
||||
</h1>
|
||||
<p class="text-xl text-gray-600 mb-6">
|
||||
{{ frameworkInfo.description }}
|
||||
</p>
|
||||
<div class="flex justify-center gap-4">
|
||||
<rs-badge variant="primary" class="text-sm px-4 py-2">
|
||||
v{{ frameworkInfo.version }}
|
||||
</rs-badge>
|
||||
<rs-badge variant="secondary" class="text-sm px-4 py-2">
|
||||
Nuxt 3 Ready
|
||||
</rs-badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Stats -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<rs-card
|
||||
v-for="(item, index) in quickSummary"
|
||||
v-for="(stat, index) in quickStats"
|
||||
:key="index"
|
||||
class="transition-all duration-300 hover:shadow-lg"
|
||||
>
|
||||
<div class="pt-5 pb-3 px-5 flex items-center gap-4">
|
||||
<div
|
||||
class="p-5 flex justify-center items-center bg-primary/20 rounded-2xl transition-all duration-300 hover:bg-primary/30"
|
||||
>
|
||||
<Icon class="text-primary text-3xl" :name="item.icon"></Icon>
|
||||
<div class="p-6 flex items-center gap-4">
|
||||
<div class="p-4 bg-primary/20 rounded-2xl">
|
||||
<Icon :name="stat.icon" size="24" class="text-primary" />
|
||||
</div>
|
||||
<div class="flex-1 truncate">
|
||||
<span class="block font-bold text-2xl leading-tight text-primary">
|
||||
{{ item.value }}
|
||||
</span>
|
||||
<span class="text-sm font-medium text-gray-600">
|
||||
{{ item.title }}
|
||||
</span>
|
||||
<div>
|
||||
<span class="block text-2xl font-bold text-primary">{{ stat.value }}</span>
|
||||
<span class="text-sm text-gray-600">{{ stat.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<!-- Gambaran Keseluruhan Pelawat Malaysia -->
|
||||
<rs-card class="col-span-1 lg:col-span-2">
|
||||
<template #header>
|
||||
<h2 class="text-xl font-bold text-primary">
|
||||
Gambaran Keseluruhan Pelawat
|
||||
</h2>
|
||||
</template>
|
||||
<template #body>
|
||||
<client-only>
|
||||
<VueApexCharts
|
||||
width="100%"
|
||||
height="350"
|
||||
type="line"
|
||||
:options="chartOptionsVisitors"
|
||||
:series="visitorData"
|
||||
></VueApexCharts>
|
||||
</client-only>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Pelawat Asing mengikut Negeri -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-primary">
|
||||
Pelawat Asing mengikut Negeri
|
||||
</h2>
|
||||
</template>
|
||||
<template #body>
|
||||
<client-only>
|
||||
<VueApexCharts
|
||||
width="100%"
|
||||
height="300"
|
||||
type="bar"
|
||||
:options="chartOptionsForeignVisitors"
|
||||
:series="[
|
||||
{ data: foreignVisitorsByState.map((item) => item.visitors) },
|
||||
]"
|
||||
></VueApexCharts>
|
||||
</client-only>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Pelancong Berulang -->
|
||||
<rs-card>
|
||||
<template #header>
|
||||
<h2 class="text-lg font-semibold text-primary">
|
||||
Kekerapan Lawatan Pelancong
|
||||
</h2>
|
||||
</template>
|
||||
<template #body>
|
||||
<client-only>
|
||||
<VueApexCharts
|
||||
width="100%"
|
||||
height="300"
|
||||
type="pie"
|
||||
:options="chartOptionsRepeatVisitors"
|
||||
:series="repeatVisitorsData.map((item) => item.percentage)"
|
||||
></VueApexCharts>
|
||||
</client-only>
|
||||
</template>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<!-- Negara Asal Pelancong Asing Teratas -->
|
||||
<rs-card class="mb-6">
|
||||
<template #header>
|
||||
<h2 class="text-xl font-bold text-primary">
|
||||
Negara Asal Pelancong Asing Teratas
|
||||
</h2>
|
||||
</template>
|
||||
<template #body>
|
||||
<client-only>
|
||||
<VueApexCharts
|
||||
width="100%"
|
||||
height="350"
|
||||
type="bar"
|
||||
:options="chartOptionsTopCountries"
|
||||
:series="[
|
||||
{
|
||||
name: 'Pelawat',
|
||||
data: topVisitorCountries.map((item) => item.visitors),
|
||||
},
|
||||
]"
|
||||
></VueApexCharts>
|
||||
</client-only>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<rs-card class="mb-6">
|
||||
<template #header>
|
||||
<h2 class="text-xl font-bold text-primary">
|
||||
Lapangan Terbang Teratas dengan Pelawat Terbanyak
|
||||
</h2>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Kedudukan
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Nama Lapangan Terbang
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Jumlah Pelawat
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<tr
|
||||
v-for="airport in topAirports"
|
||||
:key="airport.rank"
|
||||
class="hover:bg-gray-50 transition-colors duration-200"
|
||||
<!-- Development Tools -->
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-primary mb-6">Development Tools</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<rs-card
|
||||
v-for="(tool, index) in devTools"
|
||||
:key="index"
|
||||
class="transition-all duration-300 hover:shadow-lg cursor-pointer group"
|
||||
@click="navigateToTool(tool.path)"
|
||||
>
|
||||
<div class="p-6">
|
||||
<div class="flex items-start gap-4 mb-4">
|
||||
<div
|
||||
:class="getColorClasses(tool.color)"
|
||||
class="p-3 rounded-xl transition-all duration-300"
|
||||
>
|
||||
<td class="px-6 py-4 whitespace-nowrap font-medium">
|
||||
{{ airport.rank }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ airport.name }}</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap font-semibold text-primary"
|
||||
>
|
||||
{{ airport.visitors.toLocaleString() }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<Icon :name="tool.icon" size="24" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="font-semibold text-lg text-gray-800 group-hover:text-primary transition-colors">
|
||||
{{ tool.title }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-600 text-sm leading-relaxed">
|
||||
{{ tool.description }}
|
||||
</p>
|
||||
<div class="mt-4 flex items-center text-primary text-sm font-medium group-hover:gap-2 transition-all">
|
||||
<span>Open Tool</span>
|
||||
<Icon name="mdi:arrow-right" size="16" class="ml-1 group-hover:ml-2 transition-all" />
|
||||
</div>
|
||||
</div>
|
||||
</rs-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Framework Features -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<!-- Features List -->
|
||||
<rs-card>
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-bold text-primary mb-4 flex items-center gap-2">
|
||||
<Icon name="mdi:star" size="20" />
|
||||
Framework Features
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div
|
||||
v-for="(feature, index) in frameworkInfo.features"
|
||||
:key="index"
|
||||
class="flex items-center gap-2 text-sm"
|
||||
>
|
||||
<Icon name="mdi:check-circle" size="16" class="text-green-500" />
|
||||
<span>{{ feature }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</rs-card>
|
||||
|
||||
<!-- Getting Started -->
|
||||
<rs-card>
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-bold text-primary mb-4 flex items-center gap-2">
|
||||
<Icon name="mdi:rocket-launch" size="20" />
|
||||
Getting Started
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="(step, index) in gettingStarted"
|
||||
:key="index"
|
||||
class="border-l-2 border-primary/20 pl-4"
|
||||
>
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="w-6 h-6 bg-primary text-white rounded-full flex items-center justify-center text-xs font-bold">
|
||||
{{ step.step }}
|
||||
</span>
|
||||
<h4 class="font-semibold text-gray-800">{{ step.title }}</h4>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 mb-2">{{ step.description }}</p>
|
||||
<code class="text-xs bg-gray-100 px-2 py-1 rounded block font-mono">
|
||||
{{ step.command }}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</rs-card>
|
||||
</div>
|
||||
|
||||
<!-- Documentation Links -->
|
||||
<rs-card>
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-bold text-primary mb-4 flex items-center gap-2">
|
||||
<Icon name="mdi:book-open" size="20" />
|
||||
Documentation & Resources
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<a
|
||||
href="https://nuxt.com/docs"
|
||||
target="_blank"
|
||||
class="flex items-center gap-3 p-4 bg-gray-50 rounded-xl hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<Icon name="mdi:nuxt" size="24" class="text-green-500" />
|
||||
<div>
|
||||
<div class="font-semibold">Nuxt 3 Docs</div>
|
||||
<div class="text-sm text-gray-600">Official documentation</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://tailwindcss.com/docs"
|
||||
target="_blank"
|
||||
class="flex items-center gap-3 p-4 bg-gray-50 rounded-xl hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<Icon name="mdi:tailwind" size="24" class="text-blue-500" />
|
||||
<div>
|
||||
<div class="font-semibold">Tailwind CSS</div>
|
||||
<div class="text-sm text-gray-600">Utility-first CSS</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com"
|
||||
target="_blank"
|
||||
class="flex items-center gap-3 p-4 bg-gray-50 rounded-xl hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
<Icon name="mdi:github" size="24" class="text-gray-700" />
|
||||
<div>
|
||||
<div class="font-semibold">Source Code</div>
|
||||
<div class="text-sm text-gray-600">View on GitHub</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</rs-card>
|
||||
</div>
|
||||
</template>
|
||||
|
1
public/openapi.json
Normal file
1
public/openapi.json
Normal file
@ -0,0 +1 @@
|
||||
|
63
server/api/direct-openapi-json.get.js
Normal file
63
server/api/direct-openapi-json.get.js
Normal file
@ -0,0 +1,63 @@
|
||||
// Simple direct OpenAPI JSON endpoint
|
||||
// This acts as a fallback if the main server endpoint fails
|
||||
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// Set appropriate headers for JSON response
|
||||
setHeader(event, 'Content-Type', 'application/json')
|
||||
setHeader(event, 'Cache-Control', 'no-cache')
|
||||
|
||||
// Allow CORS
|
||||
setHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
setHeader(event, 'Access-Control-Allow-Methods', 'GET, OPTIONS')
|
||||
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
||||
|
||||
// Handle OPTIONS request for CORS preflight
|
||||
if (event.node.req.method === 'OPTIONS') {
|
||||
return {}
|
||||
}
|
||||
|
||||
console.log('Serving direct OpenAPI JSON...')
|
||||
|
||||
// Read the OpenAPI JSON file directly
|
||||
const openApiPath = path.join(process.cwd(), 'docs', 'openapi.json')
|
||||
|
||||
console.log(`Looking for OpenAPI file at: ${openApiPath}`)
|
||||
|
||||
if (!fs.existsSync(openApiPath)) {
|
||||
console.error(`OpenAPI file not found at: ${openApiPath}`)
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'OpenAPI file not found'
|
||||
})
|
||||
}
|
||||
|
||||
// Read and return the raw file content
|
||||
const content = fs.readFileSync(openApiPath, 'utf-8')
|
||||
|
||||
try {
|
||||
// Parse to ensure it's valid JSON
|
||||
const parsed = JSON.parse(content)
|
||||
console.log('Successfully parsed OpenAPI JSON')
|
||||
return parsed
|
||||
} catch (parseError) {
|
||||
console.error(`Failed to parse OpenAPI JSON: ${parseError.message}`)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Invalid JSON format',
|
||||
data: { error: parseError.message }
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error serving direct OpenAPI JSON:', error)
|
||||
|
||||
throw createError({
|
||||
statusCode: error.statusCode || 500,
|
||||
statusMessage: error.message || 'Failed to serve OpenAPI JSON',
|
||||
data: { error: error.message }
|
||||
})
|
||||
}
|
||||
})
|
65
server/api/docs/[...slug].get.js
Normal file
65
server/api/docs/[...slug].get.js
Normal file
@ -0,0 +1,65 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const slug = getRouterParam(event, 'slug')
|
||||
|
||||
if (!slug) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'File path is required'
|
||||
})
|
||||
}
|
||||
|
||||
// Clean the slug to prevent directory traversal
|
||||
const cleanSlug = slug.replace(/\.\./g, '').replace(/\/+/g, '/')
|
||||
const filename = cleanSlug.endsWith('.md') || cleanSlug.endsWith('.json')
|
||||
? cleanSlug
|
||||
: `${cleanSlug}.md`
|
||||
|
||||
// Construct file path
|
||||
const filePath = path.join(process.cwd(), 'docs', filename)
|
||||
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'File not found'
|
||||
})
|
||||
}
|
||||
|
||||
// Read file content
|
||||
const content = fs.readFileSync(filePath, 'utf-8')
|
||||
|
||||
// Set appropriate content type
|
||||
if (filename.endsWith('.json')) {
|
||||
setHeader(event, 'Content-Type', 'application/json')
|
||||
} else if (filename.endsWith('.md')) {
|
||||
setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
|
||||
} else {
|
||||
setHeader(event, 'Content-Type', 'text/plain; charset=utf-8')
|
||||
}
|
||||
|
||||
// Set cache headers
|
||||
setHeader(event, 'Cache-Control', 'public, max-age=300')
|
||||
|
||||
return content
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error serving docs file:', error)
|
||||
|
||||
// If it's already a createError, re-throw it
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to serve file',
|
||||
data: {
|
||||
error: error.message
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
109
server/api/docs/save-openapi.post.js
Normal file
109
server/api/docs/save-openapi.post.js
Normal file
@ -0,0 +1,109 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const body = await readBody(event)
|
||||
const { content, filename = 'openapi-custom.json', sourceType = 'custom' } = body
|
||||
|
||||
if (!content) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Content is required'
|
||||
})
|
||||
}
|
||||
|
||||
// Validate that content is valid JSON
|
||||
let parsedContent
|
||||
try {
|
||||
if (typeof content === 'string') {
|
||||
parsedContent = JSON.parse(content)
|
||||
} else {
|
||||
parsedContent = content
|
||||
}
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Invalid JSON content: ' + error.message
|
||||
})
|
||||
}
|
||||
|
||||
// Ensure docs directory exists
|
||||
const docsDir = path.join(process.cwd(), 'docs')
|
||||
if (!fs.existsSync(docsDir)) {
|
||||
fs.mkdirSync(docsDir, { recursive: true })
|
||||
}
|
||||
|
||||
// Determine the correct filename based on source type
|
||||
let cleanFilename
|
||||
if (sourceType === 'collections') {
|
||||
cleanFilename = 'openapi-collection.json'
|
||||
} else if (sourceType === 'custom') {
|
||||
cleanFilename = 'openapi-custom.json'
|
||||
} else {
|
||||
// Clean the provided filename
|
||||
cleanFilename = filename.replace(/[^a-zA-Z0-9.-]/g, '_')
|
||||
if (!cleanFilename.endsWith('.json')) {
|
||||
cleanFilename += '.json'
|
||||
}
|
||||
}
|
||||
|
||||
// Validate OpenAPI structure
|
||||
if (!parsedContent.openapi) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Invalid OpenAPI specification: missing "openapi" field'
|
||||
})
|
||||
}
|
||||
|
||||
if (!parsedContent.info) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Invalid OpenAPI specification: missing "info" field'
|
||||
})
|
||||
}
|
||||
|
||||
// Write file with formatted JSON
|
||||
const filePath = path.join(docsDir, cleanFilename)
|
||||
const formattedContent = JSON.stringify(parsedContent, null, 2)
|
||||
fs.writeFileSync(filePath, formattedContent, 'utf-8')
|
||||
|
||||
// Determine the API URL based on the filename
|
||||
let apiUrl
|
||||
if (cleanFilename === 'openapi-collection.json') {
|
||||
apiUrl = '/openapi-coll.json'
|
||||
} else if (cleanFilename === 'openapi-custom.json') {
|
||||
apiUrl = '/openapi-custom.json'
|
||||
} else {
|
||||
apiUrl = `/docs/${cleanFilename}`
|
||||
}
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
message: 'OpenAPI specification saved successfully',
|
||||
data: {
|
||||
filename: cleanFilename,
|
||||
path: `/docs/${cleanFilename}`,
|
||||
apiUrl: apiUrl,
|
||||
sourceType: sourceType,
|
||||
size: Buffer.byteLength(formattedContent, 'utf-8')
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error saving OpenAPI file:', error)
|
||||
|
||||
// If it's already a createError, re-throw it
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to save file',
|
||||
data: {
|
||||
error: error.message
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
72
server/api/openapi-coll.json.get.js
Normal file
72
server/api/openapi-coll.json.get.js
Normal file
@ -0,0 +1,72 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// Set appropriate headers for JSON response
|
||||
setHeader(event, 'Content-Type', 'application/json')
|
||||
setHeader(event, 'Cache-Control', 'public, max-age=300') // Cache for 5 minutes
|
||||
|
||||
// Read the collections OpenAPI JSON file
|
||||
const openApiPath = path.join(process.cwd(), 'docs', 'openapi-collection.json')
|
||||
|
||||
if (!fs.existsSync(openApiPath)) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Collections OpenAPI specification not found. Please generate it first from the configuration panel.'
|
||||
})
|
||||
}
|
||||
|
||||
const openApiContent = fs.readFileSync(openApiPath, 'utf-8')
|
||||
const openApiSpec = JSON.parse(openApiContent)
|
||||
|
||||
// Dynamically update server URLs based on the current request
|
||||
const host = getHeader(event, 'host') || 'localhost:3000'
|
||||
const protocol = getHeader(event, 'x-forwarded-proto') ||
|
||||
(host.includes('localhost') || host.includes('127.0.0.1') ? 'http' : 'https')
|
||||
|
||||
// Update servers in the OpenAPI spec
|
||||
openApiSpec.servers = [
|
||||
{
|
||||
url: `${protocol}://${host}/api`,
|
||||
description: 'Current Server'
|
||||
},
|
||||
{
|
||||
url: "http://localhost:3000/api",
|
||||
description: "Development Server"
|
||||
},
|
||||
{
|
||||
url: "{protocol}://{host}:{port}/api",
|
||||
description: "Configurable Server",
|
||||
variables: {
|
||||
protocol: {
|
||||
enum: ["http", "https"],
|
||||
default: protocol
|
||||
},
|
||||
host: {
|
||||
default: host.split(':')[0]
|
||||
},
|
||||
port: {
|
||||
default: host.includes(':') ? host.split(':')[1] : (protocol === 'https' ? '443' : '80')
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// Add current timestamp to info for cache busting
|
||||
openApiSpec.info.version = openApiSpec.info.version + '+' + new Date().toISOString().split('T')[0]
|
||||
|
||||
return openApiSpec
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error serving Collections OpenAPI specification:', error)
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to load Collections OpenAPI specification',
|
||||
data: {
|
||||
error: error.message
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
71
server/api/openapi-custom.json.get.js
Normal file
71
server/api/openapi-custom.json.get.js
Normal file
@ -0,0 +1,71 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// Set appropriate headers for JSON response
|
||||
setHeader(event, 'Content-Type', 'application/json')
|
||||
setHeader(event, 'Cache-Control', 'public, max-age=300') // Cache for 5 minutes
|
||||
|
||||
// Read the custom OpenAPI JSON file
|
||||
const openApiPath = path.join(process.cwd(), 'docs', 'openapi-custom.json')
|
||||
|
||||
if (!fs.existsSync(openApiPath)) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Custom OpenAPI specification not found. Please create it first from the configuration panel.'
|
||||
})
|
||||
}
|
||||
|
||||
const openApiContent = fs.readFileSync(openApiPath, 'utf-8')
|
||||
const openApiSpec = JSON.parse(openApiContent)
|
||||
|
||||
// Dynamically update server URLs based on the current request
|
||||
const host = getHeader(event, 'host') || 'localhost:3000'
|
||||
const protocol = getHeader(event, 'x-forwarded-proto') ||
|
||||
(host.includes('localhost') || host.includes('127.0.0.1') ? 'http' : 'https')
|
||||
|
||||
// Update servers in the OpenAPI spec if they don't exist or need updating
|
||||
if (!openApiSpec.servers || openApiSpec.servers.length === 0) {
|
||||
openApiSpec.servers = [
|
||||
{
|
||||
url: `${protocol}://${host}/api`,
|
||||
description: 'Current Server'
|
||||
},
|
||||
{
|
||||
url: "http://localhost:3000/api",
|
||||
description: "Development Server"
|
||||
},
|
||||
{
|
||||
url: "{protocol}://{host}:{port}/api",
|
||||
description: "Configurable Server",
|
||||
variables: {
|
||||
protocol: {
|
||||
enum: ["http", "https"],
|
||||
default: protocol
|
||||
},
|
||||
host: {
|
||||
default: host.split(':')[0]
|
||||
},
|
||||
port: {
|
||||
default: host.includes(':') ? host.split(':')[1] : (protocol === 'https' ? '443' : '80')
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return openApiSpec
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error serving Custom OpenAPI specification:', error)
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to load Custom OpenAPI specification',
|
||||
data: {
|
||||
error: error.message
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
49
server/api/openapi-file.get.js
Normal file
49
server/api/openapi-file.get.js
Normal file
@ -0,0 +1,49 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
// This is a direct endpoint to serve the OpenAPI file
|
||||
// Simpler than the dynamic route to avoid any issues
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// Set appropriate headers
|
||||
setHeader(event, 'Content-Type', 'application/json')
|
||||
setHeader(event, 'Cache-Control', 'no-cache')
|
||||
setHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
|
||||
console.log('Direct OpenAPI file endpoint called')
|
||||
|
||||
// Hard-coded path to the openapi.json file
|
||||
const filePath = path.join(process.cwd(), 'docs', 'openapi.json')
|
||||
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error(`OpenAPI file not found at ${filePath}`)
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'OpenAPI file not found'
|
||||
})
|
||||
}
|
||||
|
||||
// Read file content
|
||||
const content = fs.readFileSync(filePath, 'utf-8')
|
||||
|
||||
// Parse JSON to validate it
|
||||
try {
|
||||
const parsed = JSON.parse(content)
|
||||
return parsed
|
||||
} catch (error) {
|
||||
console.error(`Failed to parse OpenAPI JSON: ${error}`)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Invalid OpenAPI JSON format'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error serving OpenAPI file:', error)
|
||||
throw createError({
|
||||
statusCode: error.statusCode || 500,
|
||||
statusMessage: error.message || 'Failed to serve OpenAPI file'
|
||||
})
|
||||
}
|
||||
})
|
101
server/api/openapi.json.get.js
Normal file
101
server/api/openapi.json.get.js
Normal file
@ -0,0 +1,101 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
console.log('Original OpenAPI JSON endpoint called')
|
||||
// Set appropriate headers for JSON response and CORS
|
||||
setHeader(event, 'Content-Type', 'application/json')
|
||||
setHeader(event, 'Cache-Control', 'no-cache') // Disable caching for development
|
||||
|
||||
// Add CORS headers
|
||||
setHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
setHeader(event, 'Access-Control-Allow-Methods', 'GET, OPTIONS')
|
||||
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
||||
|
||||
// Handle OPTIONS request for CORS preflight
|
||||
if (event.node.req.method === 'OPTIONS') {
|
||||
return {}
|
||||
}
|
||||
|
||||
console.log('Serving OpenAPI JSON...')
|
||||
|
||||
// Read the main OpenAPI JSON file
|
||||
const openApiPath = path.join(process.cwd(), 'docs', 'openapi.json')
|
||||
console.log(`Looking for OpenAPI file at: ${openApiPath}`)
|
||||
|
||||
if (!fs.existsSync(openApiPath)) {
|
||||
console.error(`OpenAPI specification file not found at: ${openApiPath}`)
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'OpenAPI specification not found'
|
||||
})
|
||||
}
|
||||
|
||||
console.log(`OpenAPI file exists, reading content...`)
|
||||
const openApiContent = fs.readFileSync(openApiPath, 'utf-8')
|
||||
|
||||
let openApiSpec
|
||||
try {
|
||||
openApiSpec = JSON.parse(openApiContent)
|
||||
console.log('Successfully parsed OpenAPI JSON')
|
||||
} catch (parseError) {
|
||||
console.error(`Failed to parse OpenAPI JSON: ${parseError.message}`)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Invalid OpenAPI JSON format',
|
||||
data: { error: parseError.message }
|
||||
})
|
||||
}
|
||||
|
||||
// Dynamically update server URLs based on the current request
|
||||
const host = getHeader(event, 'host') || 'localhost:3000'
|
||||
const protocol = getHeader(event, 'x-forwarded-proto') ||
|
||||
(host.includes('localhost') || host.includes('127.0.0.1') ? 'http' : 'https')
|
||||
|
||||
// Update servers in the OpenAPI spec
|
||||
openApiSpec.servers = [
|
||||
{
|
||||
url: `${protocol}://${host}/api`,
|
||||
description: 'Current Server'
|
||||
},
|
||||
{
|
||||
url: "http://localhost:3000/api",
|
||||
description: "Development Server"
|
||||
},
|
||||
{
|
||||
url: "{protocol}://{host}:{port}/api",
|
||||
description: "Configurable Server",
|
||||
variables: {
|
||||
protocol: {
|
||||
enum: ["http", "https"],
|
||||
default: protocol
|
||||
},
|
||||
host: {
|
||||
default: host.split(':')[0]
|
||||
},
|
||||
port: {
|
||||
default: host.includes(':') ? host.split(':')[1] : (protocol === 'https' ? '443' : '80')
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// Add current timestamp to info for cache busting
|
||||
openApiSpec.info.version = openApiSpec.info.version + '+' + new Date().toISOString().split('T')[0]
|
||||
|
||||
console.log(`Successfully serving OpenAPI spec from ${openApiPath}`)
|
||||
return openApiSpec
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error serving OpenAPI specification:', error)
|
||||
|
||||
throw createError({
|
||||
statusCode: error.statusCode || 500,
|
||||
statusMessage: error.statusMessage || 'Failed to load OpenAPI specification',
|
||||
data: {
|
||||
error: error.message
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
381
server/api/openapi/generate-from-collections.post.js
Normal file
381
server/api/openapi/generate-from-collections.post.js
Normal file
@ -0,0 +1,381 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const body = await readBody(event)
|
||||
const { collections, config } = body
|
||||
|
||||
if (!collections || !Array.isArray(collections)) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Collections array is required'
|
||||
})
|
||||
}
|
||||
|
||||
// Set appropriate headers
|
||||
setHeader(event, 'Content-Type', 'application/json')
|
||||
|
||||
// Generate OpenAPI specification from collections
|
||||
const openApiSpec = generateOpenApiFromCollections(collections, config)
|
||||
|
||||
// Save the generated spec to file
|
||||
await saveOpenApiToFile(openApiSpec, 'openapi-collection.json')
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
message: 'OpenAPI specification generated successfully',
|
||||
data: {
|
||||
filename: 'openapi-collection.json',
|
||||
url: '/openapi-coll.json',
|
||||
spec: openApiSpec
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error generating OpenAPI from collections:', error)
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to generate OpenAPI specification',
|
||||
data: {
|
||||
error: error.message
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
async function saveOpenApiToFile(spec, filename) {
|
||||
const docsDir = path.join(process.cwd(), 'docs')
|
||||
|
||||
// Ensure docs directory exists
|
||||
if (!fs.existsSync(docsDir)) {
|
||||
fs.mkdirSync(docsDir, { recursive: true })
|
||||
}
|
||||
|
||||
const filePath = path.join(docsDir, filename)
|
||||
fs.writeFileSync(filePath, JSON.stringify(spec, null, 2), 'utf-8')
|
||||
}
|
||||
|
||||
function generateOpenApiFromCollections(collections, config = {}) {
|
||||
const spec = {
|
||||
openapi: '3.0.3',
|
||||
info: {
|
||||
title: config.title || 'Generated API Documentation from Collections',
|
||||
description: config.description || 'API documentation generated from saved collections',
|
||||
version: config.version || '1.0.0',
|
||||
contact: config.contact || {
|
||||
name: 'API Support',
|
||||
email: 'support@example.com'
|
||||
}
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
url: '{protocol}://{host}:{port}/api',
|
||||
description: 'API Server',
|
||||
variables: {
|
||||
protocol: {
|
||||
enum: ['http', 'https'],
|
||||
default: 'http'
|
||||
},
|
||||
host: {
|
||||
default: 'localhost'
|
||||
},
|
||||
port: {
|
||||
default: '3000'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
tags: [],
|
||||
paths: {},
|
||||
components: {
|
||||
schemas: {
|
||||
SuccessResponse: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
statusCode: {
|
||||
type: 'integer',
|
||||
example: 200
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
example: 'Operation successful'
|
||||
},
|
||||
data: {
|
||||
type: 'object',
|
||||
additionalProperties: true
|
||||
}
|
||||
}
|
||||
},
|
||||
ErrorResponse: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
statusCode: {
|
||||
type: 'integer',
|
||||
example: 400
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
example: 'Error message'
|
||||
},
|
||||
errors: {
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
securitySchemes: {
|
||||
bearerAuth: {
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
bearerFormat: 'JWT',
|
||||
description: 'JWT Bearer token authentication'
|
||||
}
|
||||
}
|
||||
},
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Create tags from collections
|
||||
spec.tags = collections.map(collection => ({
|
||||
name: slugify(collection.name),
|
||||
description: collection.description || `Endpoints from ${collection.name} collection`
|
||||
}))
|
||||
|
||||
// Process each collection and its requests
|
||||
collections.forEach(collection => {
|
||||
const tagName = slugify(collection.name)
|
||||
|
||||
collection.requests.forEach(request => {
|
||||
const path = extractPathFromUrl(request.url)
|
||||
const method = request.method.toLowerCase()
|
||||
|
||||
if (!spec.paths[path]) {
|
||||
spec.paths[path] = {}
|
||||
}
|
||||
|
||||
// Generate operation from request
|
||||
const operation = generateOperationFromRequest(request, tagName)
|
||||
spec.paths[path][method] = operation
|
||||
})
|
||||
})
|
||||
|
||||
return spec
|
||||
}
|
||||
|
||||
function generateOperationFromRequest(request, tagName) {
|
||||
const operation = {
|
||||
tags: [tagName],
|
||||
summary: request.name || `${request.method} request`,
|
||||
description: request.description || `${request.method} request to ${request.url}`,
|
||||
operationId: generateOperationId(request.method, request.name),
|
||||
parameters: [],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Successful response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/SuccessResponse'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
400: {
|
||||
description: 'Bad request',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
401: {
|
||||
description: 'Unauthorized',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
500: {
|
||||
description: 'Internal server error',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add query parameters
|
||||
if (request.params && Array.isArray(request.params)) {
|
||||
request.params.forEach(param => {
|
||||
if (param.active && param.key) {
|
||||
operation.parameters.push({
|
||||
name: param.key,
|
||||
in: 'query',
|
||||
description: param.description || `${param.key} parameter`,
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'string',
|
||||
example: param.value || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Add headers as parameters
|
||||
if (request.headers && Array.isArray(request.headers)) {
|
||||
request.headers.forEach(header => {
|
||||
if (header.active && header.key && !isStandardHeader(header.key)) {
|
||||
operation.parameters.push({
|
||||
name: header.key,
|
||||
in: 'header',
|
||||
description: header.description || `${header.key} header`,
|
||||
required: false,
|
||||
schema: {
|
||||
type: 'string',
|
||||
example: header.value || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Add request body for POST, PUT, PATCH methods
|
||||
if (['post', 'put', 'patch'].includes(request.method.toLowerCase())) {
|
||||
if (request.body && request.body.raw) {
|
||||
let bodySchema
|
||||
try {
|
||||
// Try to parse JSON and infer schema
|
||||
const parsedBody = JSON.parse(request.body.raw)
|
||||
bodySchema = inferSchemaFromObject(parsedBody)
|
||||
} catch {
|
||||
// Fallback to string if not valid JSON
|
||||
bodySchema = {
|
||||
type: 'string',
|
||||
example: request.body.raw
|
||||
}
|
||||
}
|
||||
|
||||
operation.requestBody = {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: bodySchema
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add security if auth is configured
|
||||
if (request.auth && request.auth.type !== 'none') {
|
||||
operation.security = [{ bearerAuth: [] }]
|
||||
}
|
||||
|
||||
return operation
|
||||
}
|
||||
|
||||
function extractPathFromUrl(url) {
|
||||
try {
|
||||
const urlObj = new URL(url)
|
||||
return urlObj.pathname
|
||||
} catch {
|
||||
// If URL is invalid, try to extract path-like string
|
||||
const pathMatch = url.match(/(?:https?:\/\/[^\/]+)?(.*)/)
|
||||
return pathMatch ? pathMatch[1] : url
|
||||
}
|
||||
}
|
||||
|
||||
function slugify(text) {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
}
|
||||
|
||||
function generateOperationId(method, name) {
|
||||
const cleanName = name.replace(/[^a-zA-Z0-9]/g, '')
|
||||
return `${method.toLowerCase()}${cleanName}`
|
||||
}
|
||||
|
||||
function isStandardHeader(headerName) {
|
||||
const standardHeaders = [
|
||||
'accept', 'accept-encoding', 'accept-language', 'authorization',
|
||||
'cache-control', 'connection', 'content-length', 'content-type',
|
||||
'cookie', 'host', 'user-agent', 'referer', 'origin'
|
||||
]
|
||||
return standardHeaders.includes(headerName.toLowerCase())
|
||||
}
|
||||
|
||||
function inferSchemaFromObject(obj) {
|
||||
if (obj === null) {
|
||||
return { type: 'null' }
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return {
|
||||
type: 'array',
|
||||
items: obj.length > 0 ? inferSchemaFromObject(obj[0]) : { type: 'string' }
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
const properties = {}
|
||||
const required = []
|
||||
|
||||
Object.keys(obj).forEach(key => {
|
||||
properties[key] = inferSchemaFromObject(obj[key])
|
||||
if (obj[key] !== null && obj[key] !== undefined) {
|
||||
required.push(key)
|
||||
}
|
||||
})
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties
|
||||
}
|
||||
|
||||
if (required.length > 0) {
|
||||
schema.required = required
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
if (typeof obj === 'string') {
|
||||
return { type: 'string', example: obj }
|
||||
}
|
||||
|
||||
if (typeof obj === 'number') {
|
||||
return Number.isInteger(obj)
|
||||
? { type: 'integer', example: obj }
|
||||
: { type: 'number', example: obj }
|
||||
}
|
||||
|
||||
if (typeof obj === 'boolean') {
|
||||
return { type: 'boolean', example: obj }
|
||||
}
|
||||
|
||||
return { type: 'string' }
|
||||
}
|
99
server/routes/docs/[...].get.js
Normal file
99
server/routes/docs/[...].get.js
Normal file
@ -0,0 +1,99 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// Set CORS headers
|
||||
setHeader(event, 'Access-Control-Allow-Origin', '*')
|
||||
setHeader(event, 'Access-Control-Allow-Methods', 'GET, OPTIONS')
|
||||
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
||||
|
||||
// Handle OPTIONS request for CORS preflight
|
||||
if (event.node.req.method === 'OPTIONS') {
|
||||
return {}
|
||||
}
|
||||
|
||||
// Get file path from URL safely
|
||||
let filePath = '';
|
||||
if (event.context.params && event.context.params._) {
|
||||
filePath = Array.isArray(event.context.params._)
|
||||
? event.context.params._.join('/')
|
||||
: event.context.params._.toString();
|
||||
}
|
||||
|
||||
console.log(`Requested docs file: ${filePath}`)
|
||||
|
||||
// Full path to file
|
||||
const fullPath = path.join(process.cwd(), 'docs', filePath)
|
||||
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
console.error(`File not found: ${fullPath}`)
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: `File not found: ${filePath}`
|
||||
})
|
||||
}
|
||||
|
||||
// Determine content type based on file extension
|
||||
const ext = path.extname(fullPath).toLowerCase()
|
||||
let contentType = 'text/plain'
|
||||
|
||||
switch (ext) {
|
||||
case '.json':
|
||||
contentType = 'application/json'
|
||||
break
|
||||
case '.md':
|
||||
contentType = 'text/markdown'
|
||||
break
|
||||
case '.yaml':
|
||||
case '.yml':
|
||||
contentType = 'application/x-yaml'
|
||||
break
|
||||
case '.html':
|
||||
contentType = 'text/html'
|
||||
break
|
||||
case '.css':
|
||||
contentType = 'text/css'
|
||||
break
|
||||
case '.js':
|
||||
contentType = 'application/javascript'
|
||||
break
|
||||
case '.png':
|
||||
contentType = 'image/png'
|
||||
break
|
||||
case '.jpg':
|
||||
case '.jpeg':
|
||||
contentType = 'image/jpeg'
|
||||
break
|
||||
}
|
||||
|
||||
// Set headers
|
||||
setHeader(event, 'Content-Type', contentType)
|
||||
setHeader(event, 'Cache-Control', 'no-cache') // Disable caching for development
|
||||
|
||||
// For JSON files, we need to validate and potentially parse
|
||||
if (ext === '.json') {
|
||||
try {
|
||||
const content = fs.readFileSync(fullPath, 'utf-8')
|
||||
return JSON.parse(content)
|
||||
} catch (error) {
|
||||
console.error(`Error parsing JSON file: ${error.message}`)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: `Invalid JSON file: ${error.message}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// For other files, return the raw content
|
||||
return fs.readFileSync(fullPath)
|
||||
} catch (error) {
|
||||
console.error(`Error serving docs file: ${error.message}`)
|
||||
throw createError({
|
||||
statusCode: error.statusCode || 500,
|
||||
statusMessage: error.message || 'Failed to serve file',
|
||||
data: { error: error.message }
|
||||
})
|
||||
}
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user