Compare commits

...

2 Commits

24 changed files with 6570 additions and 307 deletions

View 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>

View 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
View 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.

View 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

View 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.

View 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.

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -91,4 +91,4 @@ export default [
}
}
}
]
];

1381
pages/api-docs/index.vue Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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
View File

@ -0,0 +1 @@

View 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 }
})
}
})

View 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
}
})
}
})

View 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
}
})
}
})

View 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
}
})
}
})

View 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
}
})
}
})

View 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'
})
}
})

View 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
}
})
}
})

View 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' }
}

View 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 }
})
}
})