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

ubuntu host хэр хэмжээний hdd sdd зарцуулсан байгааг яаж харах вэ?

 Ubuntu дээр таны HDD/SSD-д хэдэн хэмжээний зай ашиглагдаж, хэдэн нь чөлөөтэй байгааг харах хэд хэдэн арга бий. Би хамгийн энгийн, үр дүнтэй аргуудыг зааж өгье:


1️⃣ df командыг ашиглах (Disk Free)

Таны файлын системийн зай хэр ашиглагдаж байгааг харуулна.

df -h
  • -h → human-readable, хүн ойлгох хэмжээ (GB, MB) дээр харуулна.

  • Үр дүнгийн жишээ:

Filesystem Size Used Avail Use% Mounted on /dev/sda1 50G 20G 28G 42% / tmpfs 3.9G 0 3.9G 0% /dev/shm /dev/sdb1 100G 60G 35G 65% /data

Тайлбар:

  • Size – нийт хэмжээ

  • Used – хэрэглэсэн хэмжээ

  • Avail – чөлөөтэй зай

  • Use% – ашиглалтын хувь


2️⃣ lsblk командыг ашиглах (Disk Devices)

Хард болон SSD-д хамаарах блокуудын мэдээллийг харуулна:

lsblk -f
  • NAME → диск эсвэл партишн нэр

  • SIZE → нийт хэмжээ

  • MOUNTPOINT → хаана холбогдсон (mount) байгааг харуулна

  • FSTYPE → файлын системийн төрөл (ext4, ntfs гэх мэт)

Жишээ:

NAME FSTYPE LABEL MOUNTPOINT SIZE sda ├─sda1 ext4 / 50G sdb └─sdb1 ext4 /data 100G

3️⃣ du командыг ашиглах (Folder-wise usage)

Нэг хавтас эсвэл бүх систем дээр хэдэн хэмжээний зай ашиглагдаж байгааг харах:

du -sh /path/to/folder

Жишээ: root хавтасны хэмжээг харах:

sudo du -sh /*
  • -s → summary (нэгтгэсэн дүн)

  • -h → human-readable


4️⃣ График хэрэгсэл ашиглах (опцион)

Ubuntu Desktop бол Disk Usage Analyzer (baobab) ашиглаж болно:

sudo apt install baobab baobab
  • Энэ нь графикаар диск ашиглалт харуулна, том файлуудыг хурдан тодорхойлно.


💡 Тэмдэглэл:

  • df нь файлын системийн хэмжээг харуулдаг.

  • lsblk нь диск/SSD-н физик хэмжээ, партишн-г харуулдаг.

  • du нь тухайн хавтасны зай хэр ашиглагдаж байгааг харуулна.

Monday, February 2, 2026

өөрийн веб хуудасыг facebook share яаж хийх вэ? next.js ашиглан


* /api/og-images/route.tsx

import { ImageResponse } from 'next/og';

export async function GET(request: Request) {
    // URL parse
    const { searchParams } = new URL(request.url);
    // ?title=Hello
    const title = searchParams.get('title') ?? 'Default гарчиг';

    return new ImageResponse(
        <div style={{ fontSize: 60, background: '#000', color: '#fff' }}>
            {title || `Миний гарчиг`}
        </div>,
        {
            width: 1200,
            height: 600,
        },
    );
}

* page.tsx

 import { Metadata } from 'next';

import HomeSections from './sections';

export async function generateMetadata({
    params,
}: {
    params: { slug: string };
}): Promise<Metadata> {
    // const article = await getArticle(params.slug); // DB / API

    return {
        title: `article.title`,
        description: `article.summary`,
        openGraph: {
            title: `article.title`,
            description: `article.summary`,
            url: `https://example.com/article/${params.slug}`,
            siteName: 'My Website',
            images: [
                {
                    url: `/api/og-images?title=Hello`,
                    width: 1200,
                    height: 600,
                },
            ],
            locale: 'mn_MN',
            type: 'article',
        },
    };
}

export default function HomePage() {
    return <HomeSections />;
}


* sections.tsx

'use client';

import { postRequestOptions } from '@/libs/helpers/networker';
import { useEffect, useState } from 'react';

export default function HomeSections() {
    const [accessToken, setAccessToken] = useState<string | null>(null);

    useEffect(() => {
        // Facebook SDK script нэмэх
        const loadFbSdk = () =>
            new Promise<void>((resolve) => {
                ((d, s, id) => {
                    const fjs = d.getElementsByTagName(s)[0];
                    if (d.getElementById(id)) return resolve();
                    const js = d.createElement(s) as HTMLScriptElement;
                    js.id = id;
                    js.src = 'https://connect.facebook.net/en_US/sdk.js';
                    js.onload = () => resolve();
                    d.body.appendChild(js);
                })(document, 'script', 'facebook-jssdk');
            });

        // SDK init
        loadFbSdk().then(() => {
            (window as any).FB.init({
                appId: process.env.NEXT_PUBLIC_FB_APP_ID,
                cookie: true,
                xfbml: false,
                version: 'v19.0',
            });
        });
    }, []);

    // Login function
    const fbLogin = () => {
        (window as any).FB.login(
            (response: any) => {
                if (response.authResponse) {
                    const accessToken = response.authResponse.accessToken;
                    console.log('User Access Token:', accessToken);
                    // Backend руу дамжуулахад ашиглана
                    setAccessToken(accessToken);
                } else {
                    console.log(
                        'User cancelled login or did not fully authorize.',
                    );
                    setAccessToken(null);
                }
            },
            { scope: 'public_profile,email,user_posts' },
        ); // user_posts scope зөвхөн тест токен дээр ажиллана
    };

    // Share function
    const shareArticle = (articleUrl: string) => {
        // const linkedMessage = `${articleUrl}`;
        // const shareUrl =
        //     'https://www.facebook.com/sharer/sharer.php?quote="hello"&u=' +
        //     encodeURIComponent(linkedMessage);
        // window.open(shareUrl, '_blank', 'width=600,height=400');
        const url = encodeURIComponent(`https://news.mn/r/2847199/`);
        window.open(
            `https://www.facebook.com/sharer/sharer.php?u=${url}`,
            '_blank',
            'width=600,height=500',
        );
    };

    const checkSharedPosts = async (articleUrl: string) => {
        fetch(
            '/api/check-fb-posts',
            postRequestOptions({
                accessToken,
                siteUrl: articleUrl,
            }),
        )
            .then((res) => {
                return res.json();
            })
            .then((data) => {
                console.log('checkSharedPosts:', data);
            })
            .catch((err) => {
                console.error('checkSharedPosts:', err);
            });
    };

    return (
        <div className="flex flex-col justify-center">
            <div>
                {accessToken ? (
                    <div>Нэвтэрсэн :)</div>
                ) : (
                    <div>Нэвтрээгүй (:</div>
                )}
            </div>
            <button
                className="bg-indigo-700 text-gray-300 text-md p-4 cursor-pointer"
                onClick={fbLogin}
            >
                Login with Facebook
            </button>
            <button
                className="bg-indigo-700 text-gray-300 text-md p-4 cursor-pointer"
                onClick={() =>
                    shareArticle('https://itissandboxbro.duckdns.org/admin')
                }
            >
                Share content on Facebook
            </button>
            <button
                className="bg-indigo-700 text-gray-300 text-md p-4 cursor-pointer"
                onClick={() =>
                    checkSharedPosts('https://itissandboxbro.duckdns.org/admin')
                }
            >
                Check out the posts shared on Facebook
            </button>
        </div>
    );
}



facebook дээр post share хийсэн сүүлийн 10 дотроос миний холбоос message дотор байна уу? шалгах код жишээ

 FRONT-END next.js

const checkSharedPosts = async (articleUrl: string) => {
        fetch(
            '/api/check-fb-posts',
            postRequestOptions({
                accessToken,
                siteUrl: articleUrl,
            }),
        )
            .then((res) => {
                return res.json();
            })
            .then((data) => {
                console.log('checkSharedPosts:', data);
            })
            .catch((err) => {
                console.error('checkSharedPosts:', err);
            });
    };

BACK-END nest.js

@Post('/check-fb-posts')

    async checkFbPosts(@Body() body: { accessToken: string; siteUrl: string }) {
        const { accessToken, siteUrl } = body;

        // Graph API руу сүүлийн 10 пост авах
        const url =
            `https://graph.facebook.com/me/posts` +
            `?fields=id,message,permalink_url,created_time&limit=10&access_token=${accessToken}`;
        const res = await fetch(url);
        const data = await res.json();

        // Сүүлд 10 постоос таны сайтын URL байгаа эсэхийг шалгах
        const found = data.data.find(
            (post: any) =>
                (post.message && post.message.includes(siteUrl)) ||
                (post.permalink_url && post.permalink_url.includes(siteUrl)),
        );

        return { founded: !!found, posts: data.data };
    }

Facebook 2018 оноос хойш user timeline дээр API-аар шууд post share хийхийг ХОРИГЛОСОН publish_actions гэдэг permission бүрмөсөн устсан

code:

nest.js дотор @Post('fb-post') 
    async fbPosts(@Body() body: { accessToken: string; message: string }) {
        const res = await fetch(`https://graph.facebook.com/me/feed`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                message: body.message,
                access_token: body.accessToken,
            }),
        });
        
        const result = await res.json();
       
        return { shared: true, posts: result };
    } функцээр facebook share хийх үед {
  message: `(#200) If posting to a group, requires app being installed in the group, and \\
          either publish_to_groups permission with user token, or both pages_read_engagement \\
          and pages_manage_posts permission with page token; If posting to a page, \\
          requires both pages_read_engagement and pages_manage_posts as an admin with \\
          sufficient administrative permission`,
  type: "OAuthException",
  code: 200,
  fbtrace_id: "Ano23PuN6w2b7GF1CfoQiz1",
} алдаа гараад байх юм яаж зөв ажиллуулах вэ? би өөрийн үүсгэсэн facebook app руугаа development mode дээр зүгээр share post тест хийж үзэх гэж байгаа юм! 

Яагаад энэ алдаа гарч байна вэ?

Чи одоо:

POST https://graph.facebook.com/me/feed

гэж дуудаж байна. me/feed гэдэг нь хаана post хийхийг access token чинь тодорхойлно гэсэн үг.

Facebook ингэж ойлгож байна 👇

  • Group дээр post хийх гэж байна уу?

  • Page дээр post хийх гэж байна уу?

  • User-ийн own timeline дээр post хийх гэж байна уу?

Тэгээд permission хангалтгүй болохоор (#200) алдаа өгч байна.


1️⃣ Хэрвээ ЧИ ЗӨВХӨН ӨӨРИЙН TIMELINE дээр post хийх гэж байгаа бол

Facebook 2018 оноос хойш user timeline дээр API-аар шууд post хийхийг ХОРИГЛОСОН.

👉 publish_actions гэдэг permission бүрмөсөн устсан
👉 me/feed → user timeline = ❌ API-аар БОЛОХГҮЙ

Тиймээс:

❌ API-аар “өөрийн timeline дээр пост хийнэ” гэдэг боломж байхгүй болсон


2️⃣ Group дээр post хийх бол (чи энэ алдааг авч байгаа учир магадлал өндөр)

Шаардлага:

  • App чинь тэр групп дээр installed байх

  • Permission:

    • publish_to_groups

  • Token:

    • User access token

Яаж шалгах вэ?

  1. Group → Settings

  2. Apps → Add your app

  3. App Review дээр publish_to_groups-ийг Advanced Access болгосон байх

⚠️ Development mode дээр:

  • Зөвхөн app admin / tester групп дээр post хийж чадна


3️⃣ Page дээр post хийх бол (хамгийн найдвартай зам)

Зөв setup:

Permission-ууд:

pages_read_engagement pages_manage_posts

Алхам:

  1. User token авна (admin account)

  2. Page access token авна

  3. Page ID-гаар post хийнэ

POST https://graph.facebook.com/{PAGE_ID}/feed
body: JSON.stringify({ message: body.message, access_token: PAGE_ACCESS_TOKEN, })

📌 me/feed биш {page-id}/feed


4️⃣ Чиний одоогийн кодыг яаж “зөв” болгох вэ?

Page post хийх хувилбар (ажиллана):

@Post('fb-post') async fbPosts(@Body() body: { pageAccessToken: string; message: string }) { const PAGE_ID = '1234567890'; const res = await fetch( `https://graph.facebook.com/${PAGE_ID}/feed`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: body.message, access_token: body.pageAccessToken, }), }, ); const result = await res.json(); return result; }