Wednesday, February 11, 2026

next.js дээр markdown файлыг уншаад хэрхэн html хуудас болгож харуулах вэ?

 'use client';

import { ChevronDown, FileText } from 'lucide-react';
import React, { useEffect, useState } from 'react';

import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import rehypeHighlight from 'rehype-highlight';

type TreeNode = {
    title: string;
    docPath: string;
    children?: TreeNode[];
};

const helpNodes: TreeNode[] = [
    {
        title: 'Үндсэн ойлголт',
        docPath: 'index.md',
    },
    {
        title: 'Хэрэглэгчид',
        docPath: 'users.md',
    },
];

function TreeItem({
    par,
    node,
    onLoadDoc,
}: {
    par: string;
    node: TreeNode;
    onLoadDoc: (docPath: string) => void;
}) {
    const hasChildren = node.children && node.children.length > 0;

    const handleItemClick = (e: React.MouseEvent) => {
        e.preventDefault();
        onLoadDoc(node.docPath);
    };

    return (
        <div className="ml-2">
            <div className="flex items-center gap-1">
                {hasChildren ? (
                    <ChevronDown size={16} />
                ) : (
                    <FileText size={16} />
                )}
                <a href="#" onClick={handleItemClick}>
                    <span>{node.title}</span>
                </a>
            </div>
            {hasChildren && (
                <div className="ml-4 mt-1 space-y-1">
                    {node.children!.map((child, index) => (
                        <TreeItem
                            key={par + '-' + index}
                            par={par + '-' + index}
                            node={child}
                            onLoadDoc={onLoadDoc}
                        />
                    ))}
                </div>
            )}
        </div>
    );
}

export default function HelpPage() {
    const [doc, setDoc] = useState('');

    const handleLoadDoc = async (docPath: string) => {
        try {
            const queryParams = new URLSearchParams();
            queryParams.append('md', `${docPath}`);
            fetch(`/api/md-docs/help?${queryParams.toString()}`)
                .then((res) => res.text())
                .then(async (data) => {
                    setDoc(data);
                });
        } catch (error) {
            console.error('Error loading document:', error);
        }
    };

    useEffect(() => {
        handleLoadDoc(helpNodes[0].docPath);
    }, []);

    return (
        <div className="flex-row-container gap-4">
            <div className="y-scroll-transparent w-64 bg-gray-100 border-r border-gray-200">
                <h1 className="text-xl font-semibold my-4 mx-2">Тусламж</h1>
                {helpNodes.map((node, index) => {
                    return (
                        <TreeItem
                            key={'TreeItem-' + index}
                            par={'TreeItem-' + index}
                            node={node}
                            onLoadDoc={handleLoadDoc}
                        />
                    );
                })}
                <div className="h-screen display-none">hello</div>
            </div>
            <div className="flex-scrollable-panel">
                <article
                    id="md-docs"
                    className="prose prose-slate max-w-none
                        prose-headings:font-bold
                        prose-a:text-blue-600
                        prose-pre:bg-gray-900
                        prose-pre:text-gray-100"
                >
                    <ReactMarkdown
                        rehypePlugins={[
                            rehypeRaw,
                            [rehypeHighlight, { ignoreMissing: true }],
                        ]}
                    >
                        {doc}
                    </ReactMarkdown>
                </article>
            </div>
        </div>
    );
}

Tuesday, February 10, 2026

how to response markdown file using next.js , nest.js

 on nest.js

import {
    Body,
    Controller,
    Get,
    HttpStatus,
    Post,
    Render,
    Req,
    Res,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { Throttle } from '@nestjs/throttler';
import * as fs from 'fs';
import * as path from 'path';

import { AppSessionService } from '../core/app.session.service';
import { AppConstants } from '../core/app.constants';
import { DataMemoryCacher } from '../libs/memory-cacher/data.memory-cacher';

@Controller()
export class AppController {
    constructor(
        private readonly dataCacher: DataMemoryCacher,
        private readonly appSessionService: AppSessionService,
    ) {}

    @Get('/')
    @Render('home')
    app_home(@Req() req: Request, @Res() res: Response): any {
        var hosts = [req.headers.host];
        var username = this.appSessionService.getUserName();
        let last_one = this.dataCacher.get('puu_last_one_from');

        return {
            title: 'PUU - PAAS',
            hosts: hosts,
            username: username,
            last_one: last_one ? last_one.doc_num : '-0-',
        };
    }

    @Get('/about')
    async app_about(@Req() req: Request, @Res() res: Response): Promise<any> {
        let apiDoc = '';

        // // load from cache
        // const keyCache = 'site-home-md';
        // // const dataCache = this.dataCacher.get(keyCache);
        // const dataCache = null;

        // if (dataCache) {
        //     apiDoc = dataCache;
        // } else {
        //     const mdPath = path.join(process.cwd(), 'views', 'about-api.md');
        //     apiDoc = fs.readFileSync(mdPath, 'utf8');
        //     this.dataCacher.set(keyCache, apiDoc);
        // }

        res.render('about', {
            title: 'PUU - About API',
            apiDoc,
        });
    }

    @Get('apiDocs.md')
    getApiDoc(@Res() res: Response) {
        const mdPath = path.join(process.cwd(), 'views', 'about-api.md');
        const md = fs.readFileSync(mdPath, 'utf8');
        res.type('text/markdown').send(md);
    }

    // for API

    @Throttle(AppConstants.throttler_limits.website_send)
    @Post('/language')
    app_language_sel(@Body() body: any, @Res() res: Response) {
        if (body.code && AppConstants.supported_languages.includes(body.code)) {
            this.appSessionService.setLanguage(body.code);

            return res
                .status(HttpStatus.OK)
                .json({ success: true, result: body.code });
        }
        return res.status(HttpStatus.BAD_REQUEST).json({ success: false });
    }

    @Throttle(AppConstants.throttler_limits.brute_force)
    @Get('/ip')
    ip(@Req() req: Request, @Res() res: Response) {
        return res.status(HttpStatus.OK).json({
            ip: req.ip,
            forwarded: req.headers['x-forwarded-for'],
        });
    }
}


on next.js


import fs from 'fs';
import path from 'path';
import { NextResponse } from 'next/server';

export async function GET() {
    const mdPath = path.join(process.cwd(), 'docs', 'about-api.md');
    const md = fs.readFileSync(mdPath, 'utf8');

    return new NextResponse(md, {
        headers: {
            'Content-Type': 'text/markdown; charset=utf-8',
        },
    });
}




Tuesday, February 3, 2026

PM2-г Ubuntu дээр бүрэн устгах хэдэн алхам байна

 PM2-г Ubuntu дээр бүрэн устгах хэдэн алхам байна. Энэ нь зөвхөн програмыг устгахгүй, мөн лог файлууд болон тохиргоог устгана, иймээс диск чөлөөлөхөд тохиромжтой.


1️⃣ PM2 процессыг зогсоох

pm2 kill
  • Бүх PM2 процессуудыг зогсооно.


2️⃣ PM2-г uninstall хийх

sudo npm uninstall -g pm2
  • PM2-г глобал түвшинд uninstall хийж байна.


3️⃣ PM2 тохиргоо, лог файлуудыг устгах

rm -rf ~/.pm2
  • ~/.pm2 хавтас дотор:

    • logs/ → бүх stdout/stderr log

    • dump.pm2 → процессын snapshot

    • бусад config файлууд бүгд устна


4️⃣ (Optional) PM2 logrotate module-г устгах

Хэрвээ суулгасан бол:

pm2 uninstall pm2-logrotate

5️⃣ Диск зайг шалгах

df -h
  • /home хавтасны зай чөлөөлөгдсөн эсэхийг харах.


⚠️ Санамж:

  • Энэ аргаар PM2 бүх тохиргоо, лог, процессыг устгана.

  • Хэрвээ ирээдүйд дахин ашиглах гэж байгаа бол зөвхөн лог файлуудыг truncate хийх нь илүү аюулгүй арга.

PM2 лог файлуудыг ирээдүйд автомат ротаци хийж, зай дүүрэхээс сэргийлэх тохиргоог ингэж хийх боломжтой

 PM2 лог файлуудыг ирээдүйд автомат ротаци хийж, зай дүүрэхээс сэргийлэх тохиргоог ингэж хийх боломжтой:


1️⃣ PM2 лог ротаци тохируулах

PM2 нь log rotate module-тай. Суулгаж тохируулах:

pm2 install pm2-logrotate

2️⃣ Log ротацигийн үндсэн тохиргоо

# Лог файлын хэмжээг 100MB-с хэтэрвэл шинэчлэх pm2 set pm2-logrotate:max_size 100M # Хуучин log файлуудыг 7 хоног хадгалах pm2 set pm2-logrotate:retain 7 # Ротаци хийх давтамж (минут) pm2 set pm2-logrotate:rotateInterval '0 0 * * *' # Old logs-г автоматаар цэвэрлэх pm2 set pm2-logrotate:compress true

💡 Тайлбар:

  • max_size → нэг log файлын хамгийн их хэмжээ

  • retain → хэд хоногийн log хадгалах

  • rotateInterval → cron хэлбэрээр давтамж (энд өдөрт нэг удаа)

  • compress → хуучин log-ыг zip хийж хадгалах


3️⃣ PM2-г дахин эхлүүлэх (лог ротаци ажиллуулахын тулд)

pm2 reload all