diff --git a/.github/workflows/backmerge-to-develop.yml b/.github/workflows/backmerge-to-develop.yml new file mode 100644 index 0000000..8906ac1 --- /dev/null +++ b/.github/workflows/backmerge-to-develop.yml @@ -0,0 +1,203 @@ +name: Backmerge to Develop + +on: + pull_request: + types: [closed] + branches: + - main + +jobs: + backmerge: + # Only run if PR was merged (not just closed) and came from release/* or hotfix/* branch + if: | + github.event.pull_request.merged == true && + (startsWith(github.event.pull_request.head.ref, 'release/') || + startsWith(github.event.pull_request.head.ref, 'hotfix/')) + + runs-on: ubuntu-latest + + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for all branches + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Extract version from branch name + id: version + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT + + if [[ $BRANCH_NAME == release/v* ]]; then + VERSION=$(echo $BRANCH_NAME | sed 's/release\/v//') + TYPE="release" + elif [[ $BRANCH_NAME == hotfix/v* ]]; then + VERSION=$(echo $BRANCH_NAME | sed 's/hotfix\/v//') + TYPE="hotfix" + fi + + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "type=$TYPE" >> $GITHUB_OUTPUT + + - name: Check if develop branch exists + id: check_develop + run: | + if git ls-remote --heads origin develop | grep -q develop; then + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + echo "โš ๏ธ Develop branch does not exist. Skipping backmerge." + fi + + - name: Create backmerge pull request + if: steps.check_develop.outputs.exists == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Create a unique branch name for the backmerge + BACKMERGE_BRANCH="backmerge/main-to-develop-$(date +%Y%m%d-%H%M%S)" + + # Fetch and checkout develop + git fetch origin develop:develop + + # Create new branch from develop + git checkout -b $BACKMERGE_BRANCH develop + + # Merge main into the backmerge branch + echo "Attempting to merge main into $BACKMERGE_BRANCH..." + + # Try to merge main + if git merge origin/main --no-edit; then + echo "โœ… Merge successful, no conflicts" + + # Push the backmerge branch + git push origin $BACKMERGE_BRANCH + + # Create PR using GitHub CLI + PR_TITLE="chore: backmerge v${{ steps.version.outputs.version }} to develop" + + cat > pr_body.md << 'PREOF' + ## Automated Backmerge + + This PR automatically backmerges changes from `main` to `develop` branch. + + ### Source + - **Original PR:** #${{ github.event.pull_request.number }} + - **Title:** ${{ github.event.pull_request.title }} + - **Merged by:** @${{ github.event.pull_request.merged_by.login }} + - **Merged at:** ${{ github.event.pull_request.merged_at }} + + ### Branch Information + - **Source branch:** `${{ github.event.pull_request.head.ref }}` + - **Type:** ${{ github.event.pull_request.head.ref }} + + ### Actions Required + - ๐Ÿ‘€ **Review Required** - This PR needs approval before merging + - โœ… Ensure all CI/CD checks pass + - โœ… Review changes for any conflicts or issues + - โœ… Merge after approval + + --- + *This PR was automatically created by GitHub Actions workflow.* + PREOF + + # Create PR with review requirements + # Uncomment --draft if you want PRs to be created as draft + # Add --reviewer "username1,username2" to auto-assign reviewers + gh pr create \ + --base develop \ + --head $BACKMERGE_BRANCH \ + --title "$PR_TITLE" \ + --body-file pr_body.md \ + --label "chore,auto-generated" \ + --no-maintainer-edit + + echo "โœ… Pull request created successfully" + + else + echo "โŒ Merge conflicts detected" + + # Reset merge + git merge --abort + + # Push the branch anyway for manual resolution + git push origin $BACKMERGE_BRANCH + + # Create PR with conflict warning + PR_TITLE="chore: backmerge v${{ steps.version.outputs.version }} to develop (conflicts)" + + cat > pr_conflict_body.md << 'PREOF' + ## โš ๏ธ Automated Backmerge with Conflicts + + This PR attempts to backmerge changes from `main` to `develop` branch, but **conflicts were detected**. + + ### Source + - **Original PR:** #${{ github.event.pull_request.number }} + - **Title:** ${{ github.event.pull_request.title }} + - **Merged by:** @${{ github.event.pull_request.merged_by.login }} + - **Merged at:** ${{ github.event.pull_request.merged_at }} + + ### Branch Information + - **Source branch:** `${{ github.event.pull_request.head.ref }}` + - **Type:** ${{ github.event.pull_request.head.ref }} + + ### โš ๏ธ Manual Actions Required + 1. Checkout the branch locally + 2. Merge `main` and resolve conflicts manually + 3. Push the resolved changes + 4. Request review and merge this PR + + ```bash + git fetch origin + git checkout -b ${BACKMERGE_BRANCH} origin/${BACKMERGE_BRANCH} + git merge origin/main + # Resolve conflicts in your editor + git add . + git commit + git push origin ${BACKMERGE_BRANCH} + ``` + + --- + *This PR was automatically created by GitHub Actions workflow. Manual conflict resolution is required.* + PREOF + + gh pr create \ + --base develop \ + --head $BACKMERGE_BRANCH \ + --title "$PR_TITLE" \ + --body-file pr_conflict_body.md \ + --label "chore,auto-generated,has-conflicts" \ + --no-maintainer-edit + + echo "โš ๏ธ Pull request created with conflict warning" + + # Exit with error to mark the job as failed + exit 1 + fi + + - name: Summary + if: always() + run: | + echo "## Backmerge Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Source PR:** #${{ github.event.pull_request.number }}" >> $GITHUB_STEP_SUMMARY + echo "- **Source Branch:** \`${{ github.event.pull_request.head.ref }}\`" >> $GITHUB_STEP_SUMMARY + echo "- **Target Branch:** \`develop\`" >> $GITHUB_STEP_SUMMARY + echo "- **Triggered by:** @${{ github.event.pull_request.merged_by.login }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.check_develop.outputs.exists }}" == "false" ]; then + echo "โš ๏ธ **Status:** Skipped - develop branch does not exist" >> $GITHUB_STEP_SUMMARY + else + echo "โœ… **Status:** Backmerge PR created" >> $GITHUB_STEP_SUMMARY + fi \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..0487caa --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,414 @@ +name: Deploy Documentation to Production + +on: + push: + tags: + - 'v*.*.*' # Triggers on version tags like v1.0.0, v2.1.3, etc. + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Get version from tag + run: | + echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + echo "Deploying documentation version: ${GITHUB_REF#refs/tags/}" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build documentation site + run: | + echo "๐Ÿ“ฆ Building Vue.js documentation site..." + pnpm run build + + echo "โœ… Build completed successfully" + echo "๐Ÿ“Š Build output:" + ls -la dist/ + + - name: Prepare deployment package + run: | + echo "๐Ÿ“‹ Preparing deployment package..." + + # Create deployment directory + mkdir -p deploy-package + + # Copy built files + cp -r dist/* deploy-package/ + + # Copy essential server files + cp .htaccess deploy-package/ + cp public/api-down.html deploy-package/ + + # Copy favicon if exists + cp public/favicon.ico deploy-package/ 2>/dev/null || echo "โš ๏ธ favicon.ico not found, skipping" + + # Ensure logos are included + mkdir -p deploy-package/assets + cp public/sulteng-*.webp deploy-package/assets/ 2>/dev/null || echo "โ„น๏ธ Logos already in dist or not found" + + # Create deployment info + cat > deploy-package/DEPLOY_INFO.txt << EOF + PICO SulTeng COVID-19 API Documentation + Version: ${{ env.VERSION }} + Deployed: $(date -u '+%Y-%m-%d %H:%M:%S UTC') + Built with: Vue.js + Vite + Target: Hostinger Shared Hosting + EOF + + echo "โœ… Deployment package prepared" + echo "๐Ÿ“Š Package contents:" + ls -la deploy-package/ + echo "๐Ÿ“ Package size: $(du -sh deploy-package | cut -f1)" + + - name: Setup SSH Agent + uses: webfactory/ssh-agent@v0.8.0 + with: + ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }} + log-public-key: false + + - name: Add server to known hosts + run: | + mkdir -p ~/.ssh + echo "Adding ${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PORT }} to known hosts..." + ssh-keyscan -H -p ${{ secrets.DEPLOY_PORT }} ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts + + - name: Test SSH connection + run: | + echo "Testing SSH connection to ${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PORT }}..." + ssh -p ${{ secrets.DEPLOY_PORT }} -o ConnectTimeout=10 -o BatchMode=yes ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} 'echo "SSH connection successful"' + + - name: Deploy to production server + run: | + echo "๐Ÿš€ Starting deployment of documentation ${{ env.VERSION }} to production..." + + # Create temporary deployment archive + tar -czf docs-${{ env.VERSION }}.tar.gz -C deploy-package . + + # Upload deployment package + echo "๐Ÿ“ค Uploading deployment package..." + scp -P ${{ secrets.DEPLOY_PORT }} docs-${{ env.VERSION }}.tar.gz ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/tmp/ + + # Execute deployment script on remote server + ssh -p ${{ secrets.DEPLOY_PORT }} ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} << 'EOF' + set -e + + DEPLOY_PATH="${{ secrets.DOCS_DEPLOY_PATH }}" + VERSION="${{ env.VERSION }}" + TEMP_ARCHIVE="/tmp/docs-${VERSION}.tar.gz" + BACKUP_DIR="" + + echo "๐Ÿ” Checking deployment environment..." + echo "Deploy path: $DEPLOY_PATH" + + # Create deployment directory if it doesn't exist + mkdir -p "$DEPLOY_PATH" + cd "$DEPLOY_PATH" + + echo "๐Ÿ“ Current directory contents:" + ls -la + + echo "๐Ÿ’พ Creating backup of current deployment..." + if [ -f "index.html" ]; then + BACKUP_DIR="../docs-backup-$(date +%Y%m%d_%H%M%S)" + mkdir -p "$BACKUP_DIR" + cp -r * "$BACKUP_DIR/" 2>/dev/null || echo "โš ๏ธ Some files couldn't be backed up" + echo "โœ… Backup created at: $BACKUP_DIR" + echo "BACKUP_DIR=$BACKUP_DIR" > /tmp/backup_path.txt + else + echo "โ„น๏ธ No existing deployment found, skipping backup" + echo "BACKUP_DIR=" > /tmp/backup_path.txt + fi + + echo "๐Ÿ“ฆ Extracting new deployment..." + tar -xzf "$TEMP_ARCHIVE" -C . + + echo "๐Ÿ”ง Setting proper permissions..." + find . -type f -name "*.html" -exec chmod 644 {} \; + find . -type f -name "*.css" -exec chmod 644 {} \; + find . -type f -name "*.js" -exec chmod 644 {} \; + find . -type f -name "*.json" -exec chmod 644 {} \; + find . -type f -name ".htaccess" -exec chmod 644 {} \; + find . -type d -exec chmod 755 {} \; + + echo "๐Ÿงน Cleaning up temporary files..." + rm -f "$TEMP_ARCHIVE" + + echo "โœ… New deployment files deployed!" + echo "๐Ÿ“Š Deployed files:" + ls -la | head -20 + EOF + + - name: Verify deployment and handle rollback + run: | + echo "๐Ÿ” Verifying deployment..." + sleep 5 # Give the site time to propagate + + HEALTH_CHECK_PASSED=false + + # Check if the documentation site is accessible + if [ -n "${{ secrets.DOCS_URL }}" ]; then + echo "Testing documentation site at: ${{ secrets.DOCS_URL }}" + + # Perform multiple health checks + CHECKS_PASSED=0 + TOTAL_CHECKS=3 + + for i in $(seq 1 $TOTAL_CHECKS); do + echo "Health check attempt $i/$TOTAL_CHECKS..." + + # Check if site responds with 200 + if curl -f -s -I "${{ secrets.DOCS_URL }}" | head -n1 | grep -q "200 OK"; then + echo "โœ… HTTP status check passed" + + # Check if it's serving the Vue.js app content + if curl -f -s "${{ secrets.DOCS_URL }}" | grep -q "PICO SulTeng\|pico-api-docs"; then + echo "โœ… Content verification passed" + CHECKS_PASSED=$((CHECKS_PASSED + 1)) + else + echo "โŒ Content verification failed" + fi + else + echo "โŒ HTTP status check failed" + fi + + if [ $i -lt $TOTAL_CHECKS ]; then + sleep 3 + fi + done + + # Determine if health check passed (majority of checks must pass) + if [ $CHECKS_PASSED -ge 2 ]; then + HEALTH_CHECK_PASSED=true + echo "โœ… Health checks passed ($CHECKS_PASSED/$TOTAL_CHECKS)" + else + HEALTH_CHECK_PASSED=false + echo "โŒ Health checks failed ($CHECKS_PASSED/$TOTAL_CHECKS)" + fi + else + echo "โ„น๏ธ No documentation URL configured - skipping health check" + HEALTH_CHECK_PASSED=true # Assume success if no URL to test + fi + + # Handle success/failure + ssh -p ${{ secrets.DEPLOY_PORT }} ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} << EOF + # Read backup path from previous step + if [ -f "/tmp/backup_path.txt" ]; then + BACKUP_DIR=\$(grep "BACKUP_DIR=" /tmp/backup_path.txt | cut -d'=' -f2) + else + BACKUP_DIR="" + fi + + if [ "$HEALTH_CHECK_PASSED" = "true" ]; then + echo "๐ŸŽ‰ Deployment verification successful!" + + # Clean up backup if it exists + if [ -n "\$BACKUP_DIR" ] && [ -d "\$BACKUP_DIR" ]; then + echo "๐Ÿงน Removing backup directory: \$BACKUP_DIR" + rm -rf "\$BACKUP_DIR" + echo "โœ… Backup cleaned up successfully" + fi + + # Clean up backup path file + rm -f /tmp/backup_path.txt + + echo "โœ… Deployment ${{ env.VERSION }} completed successfully!" + + else + echo "โŒ Deployment verification failed! Rolling back..." + + if [ -n "\$BACKUP_DIR" ] && [ -d "\$BACKUP_DIR" ]; then + DEPLOY_PATH="${{ secrets.DOCS_DEPLOY_PATH }}" + cd "\$DEPLOY_PATH" + + echo "๐Ÿ”„ Restoring from backup: \$BACKUP_DIR" + + # Remove failed deployment + rm -rf ./* .[^.]* 2>/dev/null || true + + # Restore backup + cp -r "\$BACKUP_DIR"/* . 2>/dev/null || echo "โš ๏ธ Some backup files couldn't be restored" + cp -r "\$BACKUP_DIR"/.[^.]* . 2>/dev/null || true + + echo "โœ… Rollback completed - previous version restored" + + # Keep backup for investigation + echo "๐Ÿ“ Backup preserved for investigation: \$BACKUP_DIR" + + else + echo "โŒ No backup available for rollback!" + echo "โš ๏ธ Manual intervention required" + fi + + # Clean up backup path file + rm -f /tmp/backup_path.txt + + echo "โŒ Deployment ${{ env.VERSION }} failed and rolled back" + exit 1 + fi + EOF + + # Exit with error if health check failed + if [ "$HEALTH_CHECK_PASSED" = "false" ]; then + echo "โŒ Deployment failed health checks and was rolled back" + exit 1 + fi + + - name: Create deployment summary + run: | + echo "## ๐Ÿ“š Documentation Deployment Summary" >> $GITHUB_STEP_SUMMARY + echo "- **Version**: ${{ env.VERSION }}" >> $GITHUB_STEP_SUMMARY + echo "- **Target**: ${{ secrets.DOCS_DEPLOY_PATH }}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: โœ… Deployed successfully" >> $GITHUB_STEP_SUMMARY + echo "- **Site**: ${{ secrets.DOCS_URL || 'URL not configured' }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### ๐Ÿ“‹ What was deployed" >> $GITHUB_STEP_SUMMARY + echo "- Vue.js documentation site" >> $GITHUB_STEP_SUMMARY + echo "- API proxy configuration (.htaccess)" >> $GITHUB_STEP_SUMMARY + echo "- Maintenance page (api-down.html)" >> $GITHUB_STEP_SUMMARY + echo "- Static assets and images" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### โœ… Next Steps" >> $GITHUB_STEP_SUMMARY + echo "1. Verify site functionality at ${{ secrets.DOCS_URL }}" >> $GITHUB_STEP_SUMMARY + echo "2. Test Vue Router navigation" >> $GITHUB_STEP_SUMMARY + echo "3. Verify API proxy is working (if backend is running)" >> $GITHUB_STEP_SUMMARY + + create-release: + runs-on: ubuntu-latest + needs: build-and-deploy + if: needs.build-and-deploy.result == 'success' + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get version and release info + id: release_info + run: | + VERSION=${GITHUB_REF#refs/tags/} + echo "version=$VERSION" >> $GITHUB_OUTPUT + + # Get the previous tag for changelog + PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -A1 "^${VERSION}$" | tail -n1) + if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" = "$VERSION" ]; then + PREVIOUS_TAG=$(git tag --sort=-version:refname | head -n2 | tail -n1) + fi + echo "previous_tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT + + - name: Generate release notes + id: release_notes + run: | + VERSION=${{ steps.release_info.outputs.version }} + PREVIOUS_TAG=${{ steps.release_info.outputs.previous_tag }} + + # Create release notes + cat > release_notes.md << 'EOF' + ## ๐Ÿ“š PICO SulTeng COVID-19 API Documentation ${{ steps.release_info.outputs.version }} + + **Deployment**: โœ… Successfully deployed to production + **Site**: ${{ secrets.DOCS_URL || 'Documentation site' }} + + ### ๐Ÿ†• What's New + + EOF + + # Get commits since last tag + if [ -n "$PREVIOUS_TAG" ]; then + echo "Changes since $PREVIOUS_TAG:" >> release_notes.md + echo "" >> release_notes.md + + git log --pretty=format:"- %s" "${PREVIOUS_TAG}..${VERSION}" | \ + grep -v "Merge branch\|Merge pull request" | \ + head -20 >> release_notes.md + else + echo "- Initial documentation release" >> release_notes.md + fi + + # Add deployment details + cat >> release_notes.md << 'EOF' + + ### ๐Ÿš€ Deployment Details + + - **Built with**: Vue.js 3 + Vite + TypeScript + - **Features**: Responsive design, bilingual support (ID/EN), API integration + - **Deployment Time**: $(date -u '+%Y-%m-%d %H:%M:%S UTC') + - **Server**: Hostinger Shared Hosting + + ### ๐Ÿ”— Quick Links + + - [Documentation Site](${{ secrets.DOCS_URL || '#' }}) + - [API Health Check](${{ secrets.DOCS_URL || 'https://pico-api.banuacoder.com' }}/api/v1/health) + - [Repository](https://github.com/banua-coder/pico-api-docs) + + ### ๐Ÿ“ฑ Features Included + + - ๐ŸŒ Bilingual support (Indonesian/English) + - ๐Ÿ“Š Interactive API documentation + - ๐Ÿ’ป Responsive design for all devices + - ๐Ÿ”„ Real-time API integration + - ๐Ÿ“ˆ COVID-19 data visualization + - ๐Ÿ–ผ๏ธ Official Central Sulawesi branding + EOF + + # Set output for GitHub Actions + echo 'RELEASE_NOTES<> $GITHUB_OUTPUT + cat release_notes.md >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION=${{ steps.release_info.outputs.version }} + + # Create the release + gh release create "$VERSION" \ + --title "๐Ÿ“š Documentation $VERSION" \ + --notes "${{ steps.release_notes.outputs.RELEASE_NOTES }}" \ + --target main + + notification: + runs-on: ubuntu-latest + needs: [build-and-deploy, create-release] + if: always() + + steps: + - name: Notify deployment status + run: | + DEPLOY_STATUS="${{ needs.build-and-deploy.result }}" + RELEASE_STATUS="${{ needs.create-release.result }}" + + if [ "$DEPLOY_STATUS" == "success" ]; then + echo "โœ… Documentation deployment successful for ${{ github.ref_name }}" + echo "๐ŸŒ Site should be available at: ${{ secrets.DOCS_URL }}" + + if [ "$RELEASE_STATUS" == "success" ]; then + echo "โœ… GitHub release created successfully" + else + echo "โš ๏ธ GitHub release creation failed, but deployment succeeded" + fi + else + echo "โŒ Documentation deployment failed for ${{ github.ref_name }}" + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/release-automation.yml b/.github/workflows/release-automation.yml new file mode 100644 index 0000000..422a1ca --- /dev/null +++ b/.github/workflows/release-automation.yml @@ -0,0 +1,218 @@ +name: Release Automation + +on: + create: + +jobs: + prepare-release: + if: | + github.event.ref_type == 'branch' && + (startsWith(github.event.ref, 'release/v') || startsWith(github.event.ref, 'hotfix/v')) + + runs-on: ubuntu-latest + + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Need full history for changelog generation + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Extract version from branch name + id: extract_version + run: | + BRANCH_NAME="${{ github.event.ref }}" + # Extract version from branch name (release/v1.2.3 or hotfix/v1.2.3) + VERSION=$(echo "$BRANCH_NAME" | sed 's/.*\/v//') + + if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "โŒ Invalid version format: $VERSION" + echo "Version must be in format x.y.z" + exit 1 + fi + + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "branch_type=$(echo "$BRANCH_NAME" | cut -d'/' -f1)" >> $GITHUB_OUTPUT + echo "โœ… Extracted version: $VERSION" + + - name: Update package.json version + run: | + VERSION="${{ steps.extract_version.outputs.version }}" + + # Update version in package.json + if [ -f "package.json" ]; then + # Use jq to update version if available, otherwise use sed + if command -v jq >/dev/null 2>&1; then + jq ".version = \"$VERSION\"" package.json > package.json.tmp && mv package.json.tmp package.json + else + sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" package.json && rm package.json.bak + fi + echo "โœ… Updated package.json to version $VERSION" + else + echo "โš ๏ธ package.json not found, skipping version update" + fi + + - name: Generate changelog + id: changelog + run: | + VERSION="${{ steps.extract_version.outputs.version }}" + + # Generate changelog using the Ruby script + echo "Generating changelog for version $VERSION..." + + # Create CHANGELOG.md if it doesn't exist + if [ ! -f "CHANGELOG.md" ]; then + echo "# Changelog" > CHANGELOG.md + echo "" >> CHANGELOG.md + echo "All notable changes to this project will be documented in this file." >> CHANGELOG.md + echo "" >> CHANGELOG.md + fi + + # Generate changelog for this version + ruby scripts/generate_changelog.rb \ + --version "$VERSION" \ + --output "CHANGELOG_NEW.md" + + # Prepend new changelog to existing one + if [ -f "CHANGELOG_NEW.md" ]; then + # Add separator + echo "" >> CHANGELOG_NEW.md + echo "---" >> CHANGELOG_NEW.md + echo "" >> CHANGELOG_NEW.md + + # Append existing changelog (skip first 3 lines if they're the header) + if [ -f "CHANGELOG.md" ]; then + tail -n +4 CHANGELOG.md >> CHANGELOG_NEW.md 2>/dev/null || cat CHANGELOG.md >> CHANGELOG_NEW.md + fi + + mv CHANGELOG_NEW.md CHANGELOG.md + echo "โœ… Changelog generated successfully" + else + echo "โŒ Failed to generate changelog" + exit 1 + fi + + # Save changelog content for PR body + echo "changelog<> $GITHUB_OUTPUT + ruby scripts/generate_changelog.rb --version "$VERSION" | head -50 >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Commit changes + id: commit + run: | + VERSION="${{ steps.extract_version.outputs.version }}" + BRANCH_TYPE="${{ steps.extract_version.outputs.branch_type }}" + + # Create a new branch for the release preparation + PREP_BRANCH="chore/prepare-${BRANCH_TYPE}-v${VERSION}" + git checkout -b "$PREP_BRANCH" + + # Stage changes + git add -A + + # Check if there are changes to commit + if git diff --staged --quiet; then + echo "No changes to commit" + echo "has_changes=false" >> $GITHUB_OUTPUT + else + # Commit with conventional commit message + if [ "$BRANCH_TYPE" = "release" ]; then + git commit -m "chore(release): prepare release v$VERSION" + else + git commit -m "fix(hotfix): prepare hotfix v$VERSION" + fi + + # Push the new branch + git push origin "$PREP_BRANCH" + + echo "has_changes=true" >> $GITHUB_OUTPUT + echo "prep_branch=$PREP_BRANCH" >> $GITHUB_OUTPUT + echo "โœ… Changes committed and pushed to $PREP_BRANCH" + fi + + - name: Create Pull Request + if: steps.commit.outputs.has_changes == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${{ steps.extract_version.outputs.version }}" + BRANCH_TYPE="${{ steps.extract_version.outputs.branch_type }}" + PREP_BRANCH="${{ steps.commit.outputs.prep_branch }}" + SOURCE_BRANCH="${{ github.event.ref }}" + + # Determine target branch (should be the release branch) + TARGET_BRANCH="$SOURCE_BRANCH" + + if [ "$BRANCH_TYPE" = "release" ]; then + PR_TITLE="chore(release): prepare release v$VERSION" + LABELS="release,auto-generated" + else + PR_TITLE="fix(hotfix): prepare hotfix v$VERSION" + LABELS="hotfix,auto-generated,priority:high" + fi + + # Create PR body + cat > pr_body.md << 'PREOF' + ## ๐Ÿš€ Prepare ${{ steps.extract_version.outputs.branch_type == 'release' && 'Release' || 'Hotfix' }} v${{ steps.extract_version.outputs.version }} + + This PR prepares the following changes for v${{ steps.extract_version.outputs.version }}: + + ### ๐Ÿ“‹ Changes Included + - โœ… Version bumped to v${{ steps.extract_version.outputs.version }} + - โœ… Changelog updated + + ### ๐Ÿ“ Changelog Preview + + ${{ steps.changelog.outputs.changelog }} + + ### โœ”๏ธ Next Steps + 1. Review and merge this PR into `${{ github.event.ref }}` + 2. Create a PR from `${{ github.event.ref }}` to `main` + 3. After merging to `main`, a tag `v${{ steps.extract_version.outputs.version }}` will be created + 4. A GitHub release will be created with the changelog + 5. Changes will be backmerged to `develop` branch + + --- + *This PR was automatically generated when the ${{ github.event.ref }} branch was created.* + PREOF + + # Create the pull request from prep branch to release/hotfix branch + gh pr create \ + --base "$TARGET_BRANCH" \ + --head "$PREP_BRANCH" \ + --title "$PR_TITLE" \ + --body-file pr_body.md \ + --label "$LABELS" + + echo "โœ… Pull request created successfully" + + - name: Summary + if: always() + run: | + echo "## Release Preparation Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Branch:** ${{ github.event.ref }}" >> $GITHUB_STEP_SUMMARY + echo "- **Type:** ${{ steps.extract_version.outputs.branch_type }}" >> $GITHUB_STEP_SUMMARY + echo "- **Version:** v${{ steps.extract_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Changes Committed:** ${{ steps.commit.outputs.has_changes == 'true' && 'โœ… Yes' || 'โŒ No' }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.commit.outputs.has_changes }}" == "true" ]; then + echo "โœ… **Status:** Pull request created for v${{ steps.extract_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + else + echo "โš ๏ธ **Status:** No changes needed" >> $GITHUB_STEP_SUMMARY + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore index ebb2677..70a1ee8 100644 --- a/.gitignore +++ b/.gitignore @@ -104,4 +104,4 @@ temp/ # Claude Code specific .claude -CLAUDE.md \ No newline at end of file +CLAUDE.mdimage.png diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fd9e364 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,101 @@ +# Changelog for v1.0.0 + +Generated on 2025-09-08 +All changes + +--- + +## โœจ Features + +- implement complete i18n translations for documentation sections ( +c24cc38) +- redesign Documentation page with sidebar layout ( +7b7f287) +- implement comprehensive national endpoint documentation ( +94f6aab) +- add MIT license and API source code reference ( +95ed65b) +- create reusable Navigation component with mobile support ( +49f042a) +- implement comprehensive responsive design and mobile navigation ( +073591f) +- add comprehensive deployment system with health checks and rollback ( +ecafbf5) +- include version in backmerge PR titles ( +a2f029c) +- add changelog generation script and release automation workflow ( +05d2b27) +- add htaccess with api proxy and spa routing configuration ( +fe0d0e3) +- add partner logos and maintenance page ( +fd23051) +- implement vue spa with modern hero, features, and responsive design ( +cee2d15) +- add html entry point with comprehensive seo meta tags ( +1fc22b6) + +## ๐Ÿ› Bug Fixes + +- create separate branch for release preparation to avoid conflicts (c197f16f) +- handle multiline commit messages in changelog generation for first release ( +5edb93c) +- update API response examples to match actual response structure ( +3d09518) +- update branding and contact links ( +3f1961d) +- resolve CSS conflict in DataSources component ( +136a545) +- update data source URLs to correct endpoints ( +25ee3b8) +- update backmerge PR titles to use conventional commit style ( +99f59c7) + +## ๐Ÿ“ Documentation + +- update README with MIT license and API source references ( +17c22a2) + +## ๐Ÿ’Ž Style + +- add trailing newlines to all code files ( +71dcbee) + +## ๐Ÿ“ฆ Build System + +- add vite, typescript, and tailwind configuration ( +9876bed) + +## ๐Ÿ‘ท CI/CD + +- add github action workflow for automatic backmerge to develop branch ( +8f7f494) + +## ๐Ÿ”ง Chores + +- Fix LaTeX rendering and clean up duplicate i18n setup ( +cc77722) +- Add LaTeX rendering for mathematical formulas in Rt calculation ( +495256f) +- Fix glossary section by removing broken i18n implementation ( +031088b) +- Fix i18n implementation in GlossarySection component ( +0aff2ef) +- Add Rt formula, references, and Vue i18n internationalization ( +98933e0) +- Add Indonesian COVID-19 terminology to glossary ( +2ea7fde) +- Refactor Documentation into subcomponents and add Glossary ( +5eed45c) +- Fix mobile layout spacing and improve Coming Soon styling ( +0685343) +- initial project setup with gitignore and readme ( +2960bc5) + +--- + +## ๐Ÿ“Š Statistics + +- Total commits: 33 +--- + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ad9b30a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Fajrian Aidil Pratama + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index ae286e2..c00760d 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ The site integrates with PICO SulTeng API: - **Base URL**: `https://pico-api.banuacoder.com/api/v1` - **Documentation**: `https://pico-api.banuacoder.com/swagger/index.html` - **Health Check**: `https://pico-api.banuacoder.com/api/v1/health` +- **Source Code**: `https://github.com/banua-coder/pico-api-go` ## Available Scripts @@ -154,9 +155,11 @@ pnpm run vue-tsc # Type check without emit ## License -This project is licensed under the ISC License. +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. --- **Created for PICO SulTeng API** -Banua Coders โ€ข Central Sulawesi COVID-19 Data API \ No newline at end of file +[Banua Coder](https://banuacoder.com) โ€ข Central Sulawesi COVID-19 Data API + +**Developer**: [Fajrian Aidil Pratama](https://github.com/ryanaidilp) diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..dae0082 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,139 @@ +#!/bin/bash + +# Deployment script for PICO SulTeng COVID-19 API Documentation +# Prepares files for deployment to Hostinger shared hosting + +echo "๐Ÿš€ Preparing PICO API Documentation for deployment..." + +# Build the Vue.js application +echo "๐Ÿ“ฆ Building Vue.js application..." +pnpm run build + +if [ ! -d "dist" ]; then + echo "โŒ Build failed - dist directory not found" + exit 1 +fi + +# Create deployment directory +echo "๐Ÿ“ Creating deployment directory..." +rm -rf deploy +mkdir -p deploy + +# Copy built files +echo "๐Ÿ“‹ Copying built files..." +cp -r dist/* deploy/ + +# Copy essential server files +echo "๐Ÿ”ง Copying server configuration files..." +cp .htaccess deploy/ +cp public/api-down.html deploy/ +cp public/favicon.ico deploy/ 2>/dev/null || echo "โš ๏ธ favicon.ico not found, skipping" + +# Copy logos and assets that might not be in dist +echo "๐Ÿ–ผ๏ธ Ensuring all assets are included..." +mkdir -p deploy/assets +cp -r public/sulteng-*.webp deploy/assets/ 2>/dev/null || echo "โš ๏ธ Logo files not found in public, checking dist" + +# Create deployment info file +cat > deploy/DEPLOY_INFO.txt << EOF +PICO SulTeng COVID-19 API Documentation +Deployment prepared on: $(date) +Built with: Vue.js + Vite +Target: Hostinger Shared Hosting + +Deployment Structure: +- All files in this directory should be uploaded to public_html/ +- .htaccess handles Vue Router routing and API proxy +- api-down.html is the maintenance page for API downtime + +Post-deployment steps: +1. Ensure .htaccess is in the document root +2. Verify Vue router is working by testing routes +3. Test API proxy functionality +4. Check that all assets load correctly +EOF + +# Note: No server startup needed - this is a static website served by Apache + +# Create health check for the static site +cat > deploy/health-check.sh << 'EOF' +#!/bin/bash +# Health check for the documentation site + +echo "๐Ÿ” Checking documentation site health..." + +# Check if main files exist +if [ -f "index.html" ]; then + echo "โœ… index.html found" +else + echo "โŒ index.html missing" + exit 1 +fi + +if [ -f ".htaccess" ]; then + echo "โœ… .htaccess found" +else + echo "โš ๏ธ .htaccess missing - Vue routing may not work" +fi + +if [ -f "api-down.html" ]; then + echo "โœ… api-down.html found" +else + echo "โš ๏ธ api-down.html missing - API maintenance page unavailable" +fi + +echo "๐Ÿ“Š Site files:" +ls -la *.html *.js *.css 2>/dev/null | head -10 + +echo "โœ… Documentation site health check complete" +EOF + +chmod +x deploy/health-check.sh + +# Verify deployment package +echo "๐Ÿ” Verifying deployment package..." +cd deploy + +# Check essential files +ESSENTIAL_FILES=("index.html" ".htaccess" "api-down.html") +MISSING_FILES=() + +for file in "${ESSENTIAL_FILES[@]}"; do + if [ ! -f "$file" ]; then + MISSING_FILES+=("$file") + fi +done + +if [ ${#MISSING_FILES[@]} -eq 0 ]; then + echo "โœ… All essential files present" +else + echo "โš ๏ธ Missing files: ${MISSING_FILES[*]}" +fi + +cd .. + +# Display deployment summary +echo "" +echo "โœ… Deployment package ready in ./deploy/ directory" +echo "" +echo "๐Ÿ“‹ Deployment Summary:" +echo "- Vue.js app built successfully" +echo "- Static files prepared for upload" +echo "- Apache .htaccess configured for SPA routing" +echo "- API maintenance page included" +echo "- Total files: $(find deploy -type f | wc -l)" +echo "- Package size: $(du -sh deploy | cut -f1)" +echo "" +echo "๐Ÿ“ค Next steps:" +echo "1. Upload all files in ./deploy/ to your Hostinger public_html/" +echo "2. Ensure .htaccess is in the document root" +echo "3. Test the site at your domain" +echo "4. Verify API proxy is working (if backend is running)" +echo "" +echo "๐ŸŒ Expected URLs:" +echo "- Documentation: https://your-domain.com/" +echo "- API Proxy: https://your-domain.com/api/v1/" +echo "- Maintenance: https://your-domain.com/api-down.html" +echo "" +echo "๐Ÿ“ Files ready for upload:" +ls -la deploy/ | head -20 \ No newline at end of file diff --git a/image.png b/image.png new file mode 100644 index 0000000..062ce7a Binary files /dev/null and b/image.png differ diff --git a/package.json b/package.json index 7c40773..e5eb540 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,9 @@ }, "dependencies": { "aos": "^2.3.4", + "katex": "^0.16.22", "vue": "^3.5.21", - "vue-i18n": "^9.14.5", + "vue-i18n": "9", "vue-router": "^4.5.1" } } diff --git a/postcss.config.js b/postcss.config.js index e99ebc2..2e7af2b 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -3,4 +3,4 @@ export default { tailwindcss: {}, autoprefixer: {}, }, -} \ No newline at end of file +} diff --git a/public/api-down.html b/public/api-down.html index b33216a..ebc6775 100644 --- a/public/api-down.html +++ b/public/api-down.html @@ -181,13 +181,13 @@

Need immediate access?

- - Contact Banua Coders + Contact Banua Coder 'โœจ Features', + 'fix' => '๐Ÿ› Bug Fixes', + 'docs' => '๐Ÿ“ Documentation', + 'style' => '๐Ÿ’Ž Style', + 'refactor' => 'โ™ป๏ธ Code Refactoring', + 'perf' => 'โšก Performance Improvements', + 'test' => 'โœ… Tests', + 'build' => '๐Ÿ“ฆ Build System', + 'ci' => '๐Ÿ‘ท CI/CD', + 'chore' => '๐Ÿ”ง Chores', + 'revert' => 'โช Reverts' + }.freeze + + BREAKING_CHANGE_HEADER = '๐Ÿ’ฅ BREAKING CHANGES' + + def initialize(options = {}) + @from_tag = options[:from_tag] + @to_ref = options[:to_ref] || 'HEAD' + @version = options[:version] + @output_file = options[:output_file] + @include_merge_commits = options[:include_merge_commits] || false + end + + def generate + # Get the latest tag if not specified + @from_tag ||= get_latest_tag + + if @from_tag.nil? || @from_tag.empty? + puts "No previous tags found. Generating changelog from beginning." + @from_tag = nil + end + + commits = get_commits + grouped_commits = group_commits_by_type(commits) + changelog = format_changelog(grouped_commits) + + if @output_file + write_to_file(changelog) + else + puts changelog + end + + changelog + end + + private + + def get_latest_tag + `git describe --tags --abbrev=0 2>/dev/null`.strip + rescue + nil + end + + def get_commits + range = @from_tag ? "#{@from_tag}..#{@to_ref}" : @to_ref + merge_flag = @include_merge_commits ? '' : '--no-merges' + + # Get commit information in a parseable format using a unique delimiter + delimiter = '|||DELIMITER|||' + format = "%H#{delimiter}%s#{delimiter}%b#{delimiter}%an#{delimiter}%ae#{delimiter}%ad" + commits_raw = `git log #{merge_flag} --format="#{format}#{delimiter}END" --date=short #{range}` + + commits = [] + commits_raw.split("#{delimiter}END").each do |commit_block| + next if commit_block.strip.empty? + + parts = commit_block.split(delimiter) + next unless parts && parts.length >= 6 + + commits << { + hash: parts[0][0..7], # Short hash + subject: parts[1] || '', + body: parts[2] || '', + author: parts[3] || '', + email: parts[4] || '', + date: parts[5] || '' + } + end + + commits + end + + def group_commits_by_type(commits) + grouped = { + breaking: [], + types: Hash.new { |h, k| h[k] = [] } + } + + commits.each do |commit| + # Parse conventional commit format + if commit[:subject] =~ /^(\w+)(?:\(([^)]+)\))?: (.+)$/ + type = $1 + scope = $2 + description = $3 + + commit_info = { + hash: commit[:hash], + scope: scope, + description: description, + author: commit[:author], + date: commit[:date] + } + + # Check for breaking changes + if commit[:subject].include?('!:') || commit[:body].to_s.downcase.include?('breaking change') + grouped[:breaking] << commit_info + end + + # Group by type + if COMMIT_TYPES.key?(type) + grouped[:types][type] << commit_info + else + grouped[:types]['chore'] << commit_info + end + else + # Non-conventional commits go to chore + grouped[:types]['chore'] << { + hash: commit[:hash], + description: commit[:subject], + author: commit[:author], + date: commit[:date] + } + end + end + + grouped + end + + def format_changelog(grouped_commits) + lines = [] + + # Header + if @version + lines << "# Changelog for v#{@version}" + else + lines << "# Changelog" + end + + lines << "" + lines << "Generated on #{Date.today.strftime('%Y-%m-%d')}" + + if @from_tag + lines << "Changes since #{@from_tag}" + else + lines << "All changes" + end + + lines << "" + lines << "---" + lines << "" + + # Breaking changes + unless grouped_commits[:breaking].empty? + lines << "## #{BREAKING_CHANGE_HEADER}" + lines << "" + grouped_commits[:breaking].each do |commit| + scope_text = commit[:scope] ? "**#{commit[:scope]}:** " : "" + lines << "- #{scope_text}#{commit[:description]} (#{commit[:hash]})" + end + lines << "" + end + + # Regular commits by type + COMMIT_TYPES.each do |type, header| + commits = grouped_commits[:types][type] + next if commits.empty? + + lines << "## #{header}" + lines << "" + + commits.each do |commit| + scope_text = commit[:scope] ? "**#{commit[:scope]}:** " : "" + lines << "- #{scope_text}#{commit[:description]} (#{commit[:hash]})" + end + lines << "" + end + + # Statistics + lines << "---" + lines << "" + lines << "## ๐Ÿ“Š Statistics" + lines << "" + + total_commits = grouped_commits[:types].values.flatten.size + lines << "- Total commits: #{total_commits}" + + if @from_tag + contributors = get_contributors + lines << "- Contributors: #{contributors.size}" + lines << "" + lines << "### Contributors" + lines << "" + contributors.each do |contributor| + lines << "- #{contributor[:name]} (#{contributor[:commits]} commits)" + end + end + + lines.join("\n") + end + + def get_contributors + range = @from_tag ? "#{@from_tag}..#{@to_ref}" : @to_ref + merge_flag = @include_merge_commits ? '' : '--no-merges' + + contributors_raw = `git shortlog -sn #{merge_flag} #{range}` + + contributors_raw.split("\n").map do |line| + if line =~ /^\s*(\d+)\s+(.+)$/ + { commits: $1.to_i, name: $2.strip } + end + end.compact.sort_by { |c| -c[:commits] } + end + + def write_to_file(content) + File.write(@output_file, content) + puts "Changelog written to #{@output_file}" + end +end + +# CLI interface +if __FILE__ == $0 + options = {} + + OptionParser.new do |opts| + opts.banner = "Usage: generate_changelog.rb [options]" + + opts.on("-f", "--from TAG", "Starting tag (default: latest tag)") do |tag| + options[:from_tag] = tag + end + + opts.on("-t", "--to REF", "Ending reference (default: HEAD)") do |ref| + options[:to_ref] = ref + end + + opts.on("-v", "--version VERSION", "Version for the changelog") do |version| + options[:version] = version + end + + opts.on("-o", "--output FILE", "Output file (default: stdout)") do |file| + options[:output_file] = file + end + + opts.on("-m", "--include-merges", "Include merge commits") do + options[:include_merge_commits] = true + end + + opts.on("-h", "--help", "Show this help message") do + puts opts + exit + end + end.parse! + + generator = ChangelogGenerator.new(options) + generator.generate +end \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 3a29c8c..3c99c87 100644 --- a/src/App.vue +++ b/src/App.vue @@ -12,4 +12,4 @@ #app { min-height: 100vh; } - \ No newline at end of file + diff --git a/src/components/DataSources.vue b/src/components/DataSources.vue new file mode 100644 index 0000000..7203c09 --- /dev/null +++ b/src/components/DataSources.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/components/MathFormula.vue b/src/components/MathFormula.vue new file mode 100644 index 0000000..7271c96 --- /dev/null +++ b/src/components/MathFormula.vue @@ -0,0 +1,37 @@ + + + + + \ No newline at end of file diff --git a/src/components/Navigation.vue b/src/components/Navigation.vue new file mode 100644 index 0000000..b4e4cd1 --- /dev/null +++ b/src/components/Navigation.vue @@ -0,0 +1,218 @@ + + + diff --git a/src/components/documentation/AuthenticationSection.vue b/src/components/documentation/AuthenticationSection.vue new file mode 100644 index 0000000..38c898a --- /dev/null +++ b/src/components/documentation/AuthenticationSection.vue @@ -0,0 +1,66 @@ + + + \ No newline at end of file diff --git a/src/components/documentation/ErrorHandlingSection.vue b/src/components/documentation/ErrorHandlingSection.vue new file mode 100644 index 0000000..ca733c4 --- /dev/null +++ b/src/components/documentation/ErrorHandlingSection.vue @@ -0,0 +1,122 @@ + + + \ No newline at end of file diff --git a/src/components/documentation/GlossarySection.vue b/src/components/documentation/GlossarySection.vue new file mode 100644 index 0000000..04d7f61 --- /dev/null +++ b/src/components/documentation/GlossarySection.vue @@ -0,0 +1,340 @@ + + + \ No newline at end of file diff --git a/src/components/documentation/NationalHistoricalSection.vue b/src/components/documentation/NationalHistoricalSection.vue new file mode 100644 index 0000000..e810dea --- /dev/null +++ b/src/components/documentation/NationalHistoricalSection.vue @@ -0,0 +1,96 @@ + + + \ No newline at end of file diff --git a/src/components/documentation/NationalLatestSection.vue b/src/components/documentation/NationalLatestSection.vue new file mode 100644 index 0000000..dd44c1f --- /dev/null +++ b/src/components/documentation/NationalLatestSection.vue @@ -0,0 +1,167 @@ + + + \ No newline at end of file diff --git a/src/components/documentation/OverviewSection.vue b/src/components/documentation/OverviewSection.vue new file mode 100644 index 0000000..eacfb32 --- /dev/null +++ b/src/components/documentation/OverviewSection.vue @@ -0,0 +1,72 @@ + + + \ No newline at end of file diff --git a/src/i18n.ts b/src/i18n.ts index 13d3811..601f790 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -17,4 +17,4 @@ const i18n = createI18n({ messages }) -export default i18n \ No newline at end of file +export default i18n diff --git a/src/locales/en.json b/src/locales/en.json index 063b2c7..0c14dfe 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -80,6 +80,188 @@ "ethicalHacker": "Ethical Hacker Indonesia - Security Partner" } }, + "documentation": { + "title": "API Documentation", + "subtitle": "Comprehensive guide for integrating with the PICO SulTeng COVID-19 API", + "overview": { + "title": "API Documentation", + "subtitle": "Comprehensive guide for integrating with the PICO SulTeng COVID-19 API. Get real-time Indonesian COVID-19 data with our RESTful endpoints.", + "apiStatus": "API Status: Operational", + "gettingStarted": "Getting Started", + "gettingStartedSub": "Quick start guide to using the PICO SulTeng API", + "baseUrl": "Base URL", + "contentType": "Content Type", + "quickExample": "Quick Example", + "noAuthRequired": "No authentication required for public endpoints" + }, + "nationalLatest": { + "title": "Get Latest National Data", + "description": "Retrieve the most recent national COVID-19 statistics for Indonesia.", + "request": "Request", + "response": "Response", + "responseFields": "Response Fields", + "tryLiveApi": "Try Live API" + }, + "nationalHistorical": { + "title": "Get Historical National Data", + "description": "Access historical national COVID-19 data with pagination support. Returns an array of data objects with the same structure as the latest endpoint.", + "queryParameters": "Query Parameters", + "parameter": "Parameter", + "type": "Type", + "description": "Description", + "default": "Default", + "exampleRequest": "Example Request", + "tryIt": "Try It", + "testInBrowser": "Test in Browser" + }, + "authentication": { + "title": "Authentication", + "subtitle": "API access and security information", + "noAuthRequired": "No Authentication Required", + "noAuthDescription": "All PICO SulTeng API endpoints are publicly accessible and do not require authentication keys or tokens.", + "rateLimiting": "Rate Limiting", + "bestPractices": "Best Practices", + "rateLimitDetails": [ + "100 requests per minute per IP", + "20 burst size for initial requests", + "No API key required", + "CORS enabled for all origins" + ], + "practiceDetails": [ + "Cache responses appropriately", + "Use HTTPS for secure connections", + "Handle rate limit responses" + ] + }, + "errorHandling": { + "title": "Error Handling", + "subtitle": "HTTP status codes and error responses", + "httpStatusCodes": "HTTP Status Codes", + "errorResponseFormat": "Error Response Format", + "errorResponse": "Error Response", + "errorFields": "Error Fields", + "statusCodes": { + "200": { "title": "OK", "description": "Request successful" }, + "400": { "title": "Bad Request", "description": "Invalid parameters or malformed request" }, + "404": { "title": "Not Found", "description": "Endpoint or resource not found" }, + "429": { "title": "Too Many Requests", "description": "Rate limit exceeded" }, + "500": { "title": "Internal Server Error", "description": "Server encountered an unexpected error" } + }, + "fieldDescriptions": { + "status": "Always \"error\" for error responses", + "code": "HTTP status code", + "message": "Human-readable error message", + "details": "Additional error information" + } + }, + "glossary": { + "title": "Glossary", + "subtitle": "Key terms and concepts used in the PICO SulTeng COVID-19 API", + "cardTitle": "API Terms & Concepts", + "cardSubtitle": "Understanding key terminology and calculations", + "reproductionRate": { + "title": "Reproduction Rate (Rt)", + "definition": "Definition", + "definitionText": "The effective reproduction number representing the average number of secondary infections caused by one infected individual at time t.", + "interpretation": "Interpretation", + "growing": "Epidemic is growing (each case infects >1 person)", + "stable": "Epidemic is stable", + "declining": "Epidemic is declining", + "calculationMethod": "Calculation Method", + "epiEstimTitle": "EpiEstim Approach (Cori et al. 2013)", + "methodDetails": [ + "Bayesian framework with Gamma priors", + "Serial interval: 5.0 ยฑ 3.0 days", + "7-day sliding window", + "95% credible intervals" + ], + "dataAvailability": "Data Availability", + "dataAvailabilityText": "Values may be null during the first 7 days or when there's insufficient data for reliable calculation.", + "formula": "Mathematical Formula", + "formulaTitle": "EpiEstim Bayesian Estimation", + "formulaDescription": "The reproduction number Rt is estimated using Bayesian inference with the following key components:", + "formulaSteps": [ + "Calculate total infectiousness:", + "Where:", + "Posterior distribution:", + "Point estimate:" + ], + "references": "References" + }, + "caseClassifications": { + "title": "Case Classifications", + "positive": { "title": "Positive Cases", "description": "Confirmed COVID-19 infections through testing" }, + "recovered": { "title": "Recovered Cases", "description": "Patients who have recovered from COVID-19" }, + "deceased": { "title": "Deceased Cases", "description": "Deaths attributed to COVID-19" }, + "active": { "title": "Active Cases", "description": "Currently infected patients (positive - recovered - deceased)" } + }, + "dataTypes": { + "title": "Data Types", + "daily": { "title": "Daily Data", "description": "New cases reported for a specific day" }, + "cumulative": { "title": "Cumulative Data", "description": "Total cases accumulated since the start of tracking" }, + "percentages": { "title": "Percentages", "description": "Distribution of cases as percentage of total positive cases" }, + "serialInterval": { "title": "Serial Interval", "description": "Time between symptom onset in successive cases in a chain of transmission" } + }, + "indonesianTerminology": { + "title": "Indonesian COVID-19 Terminology", + "subtitle": "Official Ministry of Health (Kemenkes) terminology for COVID-19 case classification", + "currentTerms": "Current Terms (Official Kemenkes Classification)", + "legacyTerms": "Legacy Terms (2020-2021)", + "officialNote": "Official basis for COVID-19 data reporting as of 2021", + "kasusSuspect": { + "name": "Kasus Suspect", + "translation": "Suspect Case", + "description": "Person from an area with local transmission within 14 days before illness, or had contact with confirmed/probable case, or severe respiratory infection requiring hospitalization with no specific identified cause." + }, + "kasusProbable": { + "name": "Kasus Probable", + "translation": "Probable Case", + "description": "Clinical COVID-19 case with severe symptoms, ARDS or severe respiratory distress, not yet laboratory tested via RT-PCR." + }, + "kontakErat": { + "name": "Kontak Erat", + "translation": "Close Contact", + "description": "Person who contacted a confirmed or probable case within the infectious period." + }, + "kasusKonfirmasi": { + "name": "Kasus Konfirmasi", + "translation": "Confirmed Case", + "description": "Positive RT-PCR test result with two subcategories: symptomatic and asymptomatic cases." + }, + "otg": { + "acronym": "OTG", + "fullName": "Orang Tanpa Gejala", + "translation": "Asymptomatic Person", + "description": "Legacy term: Individuals who test positive for COVID-19 but show no symptoms. Now classified under confirmed cases (asymptomatic).", + "status": "legacy", + "replacedBy": "Kasus Konfirmasi (Asimtomatis)" + }, + "odp": { + "acronym": "ODP", + "fullName": "Orang Dalam Pemantauan", + "translation": "Person Under Observation", + "description": "Legacy term: Individuals monitored for 14 days after exposure. Now classified as Suspect Cases.", + "status": "legacy", + "replacedBy": "Kasus Suspect" + }, + "pdp": { + "acronym": "PDP", + "fullName": "Pasien Dalam Pengawasan", + "translation": "Patient Under Supervision", + "description": "Legacy term: Patients with symptoms requiring medical supervision. Now classified as Probable Cases.", + "status": "legacy", + "replacedBy": "Kasus Probable" + }, + "classificationProgression": { + "title": "Official Classification System", + "description": "Kemenkes updated terminology in 2021 for more precise epidemiological classification and reporting mechanisms", + "officialQuote": "This calculation basis will be used starting today for COVID-19 data reporting", + "currentFlow": "Official: Suspect โ†’ Probable โ†’ Confirmed (Symptomatic/Asymptomatic)", + "legacyFlow": "Legacy: ODP โ†’ PDP โ†’ OTG โ†’ Confirmed" + } + } + } + }, "footer": { "description": "Comprehensive COVID-19 data API for Central Sulawesi, providing real-time statistics and historical data.", "createdBy": "Created by {author}", diff --git a/src/locales/id.json b/src/locales/id.json index a470f24..dfc9c9c 100644 --- a/src/locales/id.json +++ b/src/locales/id.json @@ -80,6 +80,188 @@ "ethicalHacker": "Ethical Hacker Indonesia - Partner Keamanan" } }, + "documentation": { + "title": "Dokumentasi API", + "subtitle": "Panduan komprehensif untuk integrasi dengan API COVID-19 PICO SulTeng", + "overview": { + "title": "Dokumentasi API", + "subtitle": "Panduan komprehensif untuk integrasi dengan API COVID-19 PICO SulTeng. Dapatkan data COVID-19 Indonesia real-time dengan endpoint RESTful kami.", + "apiStatus": "Status API: Operasional", + "gettingStarted": "Memulai", + "gettingStartedSub": "Panduan cepat menggunakan API PICO SulTeng", + "baseUrl": "URL Dasar", + "contentType": "Tipe Konten", + "quickExample": "Contoh Cepat", + "noAuthRequired": "Tidak memerlukan autentikasi untuk endpoint publik" + }, + "nationalLatest": { + "title": "Dapatkan Data Nasional Terbaru", + "description": "Mengambil statistik COVID-19 nasional terbaru untuk Indonesia.", + "request": "Permintaan", + "response": "Respons", + "responseFields": "Field Respons", + "tryLiveApi": "Coba API Langsung" + }, + "nationalHistorical": { + "title": "Dapatkan Data Nasional Historis", + "description": "Akses data COVID-19 nasional historis dengan dukungan paginasi. Mengembalikan array objek data dengan struktur yang sama dengan endpoint terbaru.", + "queryParameters": "Parameter Query", + "parameter": "Parameter", + "type": "Tipe", + "description": "Deskripsi", + "default": "Default", + "exampleRequest": "Contoh Permintaan", + "tryIt": "Coba", + "testInBrowser": "Tes di Browser" + }, + "authentication": { + "title": "Autentikasi", + "subtitle": "Informasi akses API dan keamanan", + "noAuthRequired": "Tidak Memerlukan Autentikasi", + "noAuthDescription": "Semua endpoint API PICO SulTeng dapat diakses publik dan tidak memerlukan kunci autentikasi atau token.", + "rateLimiting": "Pembatasan Rate", + "bestPractices": "Praktik Terbaik", + "rateLimitDetails": [ + "100 permintaan per menit per IP", + "20 burst size untuk permintaan awal", + "Tidak memerlukan kunci API", + "CORS diaktifkan untuk semua origin" + ], + "practiceDetails": [ + "Cache respons dengan tepat", + "Gunakan HTTPS untuk koneksi aman", + "Tangani respons batas rate" + ] + }, + "errorHandling": { + "title": "Penanganan Error", + "subtitle": "Kode status HTTP dan respons error", + "httpStatusCodes": "Kode Status HTTP", + "errorResponseFormat": "Format Respons Error", + "errorResponse": "Respons Error", + "errorFields": "Field Error", + "statusCodes": { + "200": { "title": "OK", "description": "Permintaan berhasil" }, + "400": { "title": "Bad Request", "description": "Parameter tidak valid atau permintaan salah format" }, + "404": { "title": "Not Found", "description": "Endpoint atau resource tidak ditemukan" }, + "429": { "title": "Too Many Requests", "description": "Batas rate terlampaui" }, + "500": { "title": "Internal Server Error", "description": "Server mengalami error yang tidak terduga" } + }, + "fieldDescriptions": { + "status": "Selalu \"error\" untuk respons error", + "code": "Kode status HTTP", + "message": "Pesan error yang dapat dibaca manusia", + "details": "Informasi error tambahan" + } + }, + "glossary": { + "title": "Glosarium", + "subtitle": "Istilah dan konsep kunci yang digunakan dalam API COVID-19 PICO SulTeng", + "cardTitle": "Istilah & Konsep API", + "cardSubtitle": "Memahami terminologi dan perhitungan kunci", + "reproductionRate": { + "title": "Angka Reproduksi (Rt)", + "definition": "Definisi", + "definitionText": "Angka reproduksi efektif yang mewakili rata-rata infeksi sekunder yang disebabkan oleh satu individu terinfeksi pada waktu t.", + "interpretation": "Interpretasi", + "growing": "Epidemi berkembang (setiap kasus menginfeksi >1 orang)", + "stable": "Epidemi stabil", + "declining": "Epidemi menurun", + "calculationMethod": "Metode Perhitungan", + "epiEstimTitle": "Pendekatan EpiEstim (Cori et al. 2013)", + "methodDetails": [ + "Kerangka Bayesian dengan prior Gamma", + "Interval serial: 5,0 ยฑ 3,0 hari", + "Jendela geser 7 hari", + "Interval kredibel 95%" + ], + "dataAvailability": "Ketersediaan Data", + "dataAvailabilityText": "Nilai dapat berupa null selama 7 hari pertama atau ketika data tidak mencukupi untuk perhitungan yang dapat diandalkan.", + "formula": "Formula Matematika", + "formulaTitle": "Estimasi Bayesian EpiEstim", + "formulaDescription": "Angka reproduksi Rt diestimasi menggunakan inferensi Bayesian dengan komponen kunci berikut:", + "formulaSteps": [ + "Hitung total infektivitas:", + "Dimana:", + "Distribusi posterior:", + "Estimasi titik:" + ], + "references": "Referensi" + }, + "caseClassifications": { + "title": "Klasifikasi Kasus", + "positive": { "title": "Kasus Positif", "description": "Infeksi COVID-19 yang dikonfirmasi melalui tes" }, + "recovered": { "title": "Kasus Sembuh", "description": "Pasien yang telah sembuh dari COVID-19" }, + "deceased": { "title": "Kasus Meninggal", "description": "Kematian yang disebabkan oleh COVID-19" }, + "active": { "title": "Kasus Aktif", "description": "Pasien yang saat ini terinfeksi (positif - sembuh - meninggal)" } + }, + "dataTypes": { + "title": "Tipe Data", + "daily": { "title": "Data Harian", "description": "Kasus baru yang dilaporkan untuk hari tertentu" }, + "cumulative": { "title": "Data Kumulatif", "description": "Total kasus yang terakumulasi sejak awal pelacakan" }, + "percentages": { "title": "Persentase", "description": "Distribusi kasus sebagai persentase dari total kasus positif" }, + "serialInterval": { "title": "Interval Serial", "description": "Waktu antara onset gejala pada kasus berturut-turut dalam rantai transmisi" } + }, + "indonesianTerminology": { + "title": "Terminologi COVID-19 Indonesia", + "subtitle": "Terminologi resmi Kementerian Kesehatan (Kemenkes) untuk klasifikasi kasus COVID-19", + "currentTerms": "Istilah Terkini (Klasifikasi Resmi Kemenkes)", + "legacyTerms": "Istilah Lama (2020-2021)", + "officialNote": "Dasar resmi untuk pelaporan data COVID-19 sejak 2021", + "kasusSuspect": { + "name": "Kasus Suspect", + "translation": "Suspect Case", + "description": "Orang dari daerah transmisi lokal dalam 14 hari sebelum sakit, atau kontak dengan kasus konfirmasi/probable, atau infeksi pernapasan berat memerlukan rawat inap tanpa penyebab spesifik." + }, + "kasusProbable": { + "name": "Kasus Probable", + "translation": "Probable Case", + "description": "Kasus klinis COVID-19 dengan gejala berat, ARDS atau gangguan pernapasan berat, belum diuji laboratorium melalui RT-PCR." + }, + "kontakErat": { + "name": "Kontak Erat", + "translation": "Close Contact", + "description": "Orang yang kontak dengan kasus konfirmasi atau probable dalam periode infeksius." + }, + "kasusKonfirmasi": { + "name": "Kasus Konfirmasi", + "translation": "Confirmed Case", + "description": "Hasil tes RT-PCR positif dengan dua subkategori: kasus simtomatis dan asimtomatis." + }, + "otg": { + "acronym": "OTG", + "fullName": "Orang Tanpa Gejala", + "translation": "Asymptomatic Person", + "description": "Istilah lama: Individu positif COVID-19 tanpa gejala. Kini diklasifikasikan sebagai kasus konfirmasi (asimtomatis).", + "status": "legacy", + "replacedBy": "Kasus Konfirmasi (Asimtomatis)" + }, + "odp": { + "acronym": "ODP", + "fullName": "Orang Dalam Pemantauan", + "translation": "Person Under Observation", + "description": "Istilah lama: Individu dipantau 14 hari setelah paparan. Kini diklasifikasikan sebagai Kasus Suspect.", + "status": "legacy", + "replacedBy": "Kasus Suspect" + }, + "pdp": { + "acronym": "PDP", + "fullName": "Pasien Dalam Pengawasan", + "translation": "Patient Under Supervision", + "description": "Istilah lama: Pasien bergejala memerlukan pengawasan medis. Kini diklasifikasikan sebagai Kasus Probable.", + "status": "legacy", + "replacedBy": "Kasus Probable" + }, + "classificationProgression": { + "title": "Sistem Klasifikasi Resmi", + "description": "Kemenkes memperbarui terminologi pada 2021 untuk klasifikasi epidemiologi dan mekanisme pelaporan yang lebih presisi", + "officialQuote": "Basis perhitungan ini akan digunakan mulai hari ini untuk pelaporan data COVID-19", + "currentFlow": "Resmi: Suspect โ†’ Probable โ†’ Konfirmasi (Simtomatis/Asimtomatis)", + "legacyFlow": "Lama: ODP โ†’ PDP โ†’ OTG โ†’ Konfirmasi" + } + } + } + }, "footer": { "description": "API data COVID-19 komprehensif untuk Sulawesi Tengah, menyediakan statistik real-time dan data historis.", "createdBy": "Dibuat oleh {author}", diff --git a/src/main.ts b/src/main.ts index da9fd95..95c51d9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,4 +9,4 @@ const app = createApp(App) app.use(router) app.use(i18n) -app.mount('#app') \ No newline at end of file +app.mount('#app') diff --git a/src/router/index.ts b/src/router/index.ts index 2dc1d09..c8e3910 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -24,4 +24,4 @@ const router = createRouter({ ] }) -export default router \ No newline at end of file +export default router diff --git a/src/views/ApiReference.vue b/src/views/ApiReference.vue index 6cbc90b..e0f4077 100644 --- a/src/views/ApiReference.vue +++ b/src/views/ApiReference.vue @@ -1,27 +1,9 @@ \ No newline at end of file +import Navigation from '@/components/Navigation.vue' + diff --git a/src/views/Documentation.vue b/src/views/Documentation.vue index 2260fb8..cbfe598 100644 --- a/src/views/Documentation.vue +++ b/src/views/Documentation.vue @@ -1,54 +1,228 @@ \ No newline at end of file diff --git a/src/views/Home.vue b/src/views/Home.vue index a8d7ad3..700936d 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -1,46 +1,7 @@ \ No newline at end of file + diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 37dae94..323c78a 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -4,4 +4,4 @@ declare module '*.vue' { import type { DefineComponent } from 'vue' const component: DefineComponent<{}, {}, any> export default component -} \ No newline at end of file +} diff --git a/tailwind.config.js b/tailwind.config.js index 56892f7..232189b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -28,4 +28,4 @@ export default { plugins: [ require('@tailwindcss/typography'), ], -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index 4d9e403..430fe8d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,4 +22,4 @@ }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "references": [{ "path": "./tsconfig.node.json" }] -} \ No newline at end of file +} diff --git a/tsconfig.node.json b/tsconfig.node.json index 4eb43d0..97ede7e 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -8,4 +8,4 @@ "strict": true }, "include": ["vite.config.ts"] -} \ No newline at end of file +} diff --git a/vite.config.ts b/vite.config.ts index f13b038..48d1d26 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -18,4 +18,4 @@ export default defineConfig({ }, }, }, -}) \ No newline at end of file +})