Комментарии в блоге

Добавлены комментарии в блоге.

В качестве движка используется gitalk.

Как его добавить в light-тему Hexo:

  1. Создайте репозиторий для хранения Issues. Я создал с названием gitalk.

  2. Создайте приложение в github: тынц.

    New GitHub Application

    Это приложение потом можно будет увидеть в списке Settings / Developer settings.

  3. Сгенерируйте “секрет” (Generate a new client secret); запомните (запишите) его.

  4. В light-теме Hexo в layout\_partial создайте файл gitalk_comment.ejs со следующим содержанием:

    <div id="gitalk-container" style="margin: 30px;"></div>
    <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
    <script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/gh/gangdong/gangdong.github.io@dev/assets/js/md5.min.js"></script>
    <script>
    var gitalk = new Gitalk({
    clientID: '<%= gtConfig.clientID %>',
    clientSecret: '<%= gtConfig.clientSecret %>',
    repo: '<%= gtConfig.repo %>',
    owner: '<%= gtConfig.owner %>',
    admin: ['<%= gtConfig.admin %>'],
    id: md5(location.pathname), // Ensure uniqueness and length less than 50
    pagerDirection: '<%= gtConfig.pagerDirection %>',
    distractionFreeMode: false // Facebook-like distraction free mode
    })
    gitalk.render('gitalk-container')
    </script>
  5. В layout\_partial\comment.ejs подключите gitalk_comment.ejs:

    <% if (theme.comment_provider == "gitalk") {
    if (config.gitalk) { %>
    <%- partial('_partial/gitalk_comment', {gtConfig: config.gitalk}) %>
    <% } %>
    <% } %>
  6. В _config.yml light-темы включите использование gitalk-комментариев:

    comment_provider: gitalk
  7. В корневом _config.yml вашего блога выполните настройку gitalk:

    # gitalk comment
    gitalk:
    clientID: your_client_id
    clientSecret: your_secrat
    repo: your_repo_name
    owner: your_github_user
    admin: your_github_user
    pagerDirection: first
    • clientID - значение Client ID из настроек приложения, созданного на шаге 2.
    • clientSecret - “секрет” (Client secret), созданный на шаге 3.
    • repo - название репозитория (шаг 1).
    • owner и admin - имя Вашего пользователя в Github.

OpenVPN APK

OpenVPN for Android 0.7.33

Если при установке соединения вылазит ошибка:

10:57 AM OpenSSL: error:0A00018E:SSL routines::ca md too weak

, то нужно отредактировать настройки на вкладке ADVANCED: промотать до пункта Enable Custom Options, включить его и ввести в Custom Options:

tls-cipher "DEFAULT:@SECLEVEL=0"

Поиск в блоге

Я считаю, что поиск по блогу - “must-have feature”. К сожалению, в выбранной мною простенькой теме hexo-light поиска не предусмотрено. Поэтому, нужно либо использовать сервисы типа Гугл или Яндекс, либо автоматически создавать индексный файл (xml или json) каждый раз при перегенерации блога и реализовать поиск на клиенте с помощью javascript. Я решил идти по второму пути.

Для генерации индекса выбран плагин для hexo: hexo-generator-searchdb. Устанавливается он с помощью:

npm install hexo-generator-searchdb

Конфигурирование делается в _config.yml блога:

search:
path: search.json
field: post
content: true
format: html

Следующим шагом нужно создать страницу для поиска. Делается это так:

hexo new page "search"

В source каталоге создастся подкаталог search и в нем index.md. Содержание надо сделать наподобие такого:

---
title:
date: 2022-02-14 17:31:44
type: search
layout: search
---

Я добавил layout search в тему hexo-theme-light-plus. title лучше оставить пустым, тогда в качестве заголовка страницы поиска будет использоваться локализованная строка “search”.

Скрипт для поиска лежит в js/local-search.js. Я его сделал на основе вот этого. Убрал “popup”. Сделал постраничный показ результатов поиска (по 10 на страницу). Настройки в самом local-search.js:

const CONFIG = {
root : '/',
path : 'search.json',
localsearch : {
trigger : 'auto',
top_n_per_article : 1,
unescape : false,
preload : true,
results_per_page : 10
}
};

В общем, получилось так: поиск.

Обновление MySQL Server на CentOS

При обновлении MySQL Server на CentOS вылезла ошибка:

The GPG keys listed for the "MySQL 8.0 Community Server" repository are already installed but they are not correct for this package.
Check that the correct key URLs are configured for this repository.

Failing package is: mysql-community-icu-data-files-8.0.28-1.el7.x86_64
GPG Keys are configured as: file:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysql

Победить можно выполнив команду:

rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022

Переезд

Гугл прислал письмо о том, что бесплатная версия G Suite прекратит работать с 1 июля 2022 года. И это послужило толчком для переезда блога с blogspot на другую платформу. После недолгих поисков подходящего движка для блога, я остановил свой выбор на Hexo (https://hexo.io).

Тему для блога я выбрал простенькую “light”, которую пришлось немного “допилить”. “Допиленная” тема получила название “light plus” и лежит тут: https://github.com/coolsoftware/hexo-theme-light-plus

В теме было сделано следующее:

  • Заголовок оформлен как блок.
  • В заголовок добавлена картинка.
  • Добавлен блок контактов с возможность выводить в нем ссылку на репозиторий github, контактный email и ссылку на сайт.
  • Добавлен блок “Избранные сообщения”.
  • В “Архив” добавлена навигация по годам, месяцам и пролистывание (кнопки “Вперед”, “Назад”).

Кроме работы над темой, пришлось разбираться с импортом записей из старого блога. Для миграции из blogger в hexo нашелся “hexo-migrator-blogger”. Однако, он сильно устарел, некоторые зависимости уже не устанавливаются. А кроме того, мне хотелось, чтобы сохранились все внешние ссылки на записи. Поэтому пришлось писать свой “мигратор”: https://github.com/coolsoftware/hexo-migrator-blogspot.

Чтобы сохранить ссылки на существующие посты нужно:

  1. В новом блоге сделать такую же структуру записей, что была в старом, т.е. /<год>/<месяц>/<заголовок>. _config.yml:

    permalink: :year/:month/:name/
    new_post_name: :year/:month/:title.md
  2. Прописать mod_rewrite-правила (для Апача):

    RewriteEngine on
    RewriteRule ^(/[0-9]{4}/[0-9]{2}/[^/]+).html$ $1/ [NC,NE,L,R=301]
    RewriteRule ^/([0-9]{4})/$ /archives/$1/ [NC,NE,L,R=301]
    RewriteRule ^/([0-9]{4})/([0-9]{2})/$ /archives/$1/$2/ [NC,NE,L,R=301]

В одну строку

Генерация случайной строки (например, пароля) в nodejs в одну строку:

(function(length,charset){return [...Array(length)].reduce(x=>x+charset[Math.floor(Math.random()*charset.length)],'')})(16,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789');

Base64 -> hex (nodejs)

(function(b64){return Buffer.from(b64, 'base64').toString('hex');})('AQAB');

Base64url -> hex (nodejs)

(function(b64url){return Buffer.from(b64url.replaceAll(/[_-]/g, function(m){return '/+'['_-'.indexOf(m)];}), 'base64').toString('hex'); })('AQAB_A=');

String -> Base64 (nodejs)

(function(s){return Buffer.from(s).toString('base64');})('test');

String -> Base64url (nodejs)

(function(s){return Buffer.from(s).toString('base64').replaceAll(/[+/=]/g,m=>'-_'['+/'.indexOf(m)]||'');})('test');

SHA-256 Digest (nodejs)

crypto.createHash('sha256').update('12345').digest().toString('hex');

Переименование файлов: “Сериал.Part-1.mkv” -> “Сериал.S01E01.mkv”, “Сериал.Part-2.mkv” -> “Сериал.S01E02.mkv” и т.д. (bash)

for i in *.mkv; do mv $i $(echo $i | sed 's/Part-\([0-9]\)/S01E0\1/'); done;

ROT13 (“шифр Цезаря”)

(function(s){return s.split('').reduce((x,y) => x + ('nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM'['abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.indexOf(y)] || y), '')})('Test12345');

Base64 to Pascal array of bytes:

(function(b64){return Buffer.from(b64, 'base64').reduce((s,b,i)=>s+(i?(i%16==0?',\n':', '):'')+'$'+b.toString('16').padStart(2, '0'), '')})('AQAB');

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru

Mongo: Aggregation with Pagination

Допустим у нас есть такая коллекция users в Mongo DB:

[
{
username: "john.doe",
roles: ["user"],
contacts: [
{
name: "John Doe",
email: "johndoe@gmail.com"
},
{
name: "J.D.",
email: "jd@gmail.com",
phone: "+11111111111"
}
]
},
{
username: "jack.sparrow",
roles: ["manager"],
contacts: [
{
name: "Jack Sparrow (gmail)",
email: "jack.sparrow@gmail.com",
},
{
name: "Jack Sparrow (hotmail)",
email: "jack.sparrow@hotmail.com",
}
]
}
]

И мы хотим организовать просмотр всех контактов всех пользователей в одной таблице с разбивкой по страницам (“pagination”). То есть так:

Чтобы получить все контакты можно воспользоваться aggregation с $unwind.

Еще нам нужно посчитать количество всех контактов, после чего “вырезать” только те, которые нужно показать в выбранной странице. Для того, чтобы выполнить обе эти операции одним запросом, воспользуемся чудесной пайплайн-стадией $facet. Получится так:

db.collection('users').aggregate([
{"$unwind":"$contacts"},
{"$facet":{
"meta":[
{"$count": "totalCount"}
],
"data":[
{"$sort":{'contacts.name':1,'username':1}},
{"$skip":firstRow-1},
{"$limit":page_size},
{"$replaceRoot":
{"newRoot":{
"$mergeObjects":[
{
"username":"$username",
},
"$contacts"
]
}}
},
],
}},
]);

page_size - это количество записей на странице.

firstRow - это номер первой записи на странице. Вычисляется он так:

const firstRow = (page > 0 ? ((page-1) * page_size) : 0) + 1;

Вместо $unwind могут быть другие пайплайн-стадии, которые выбирают нужные для показа данные. Основной же прием, который я хотел продемонстрировать в этом посте, - это использование $facet.

Полный пример можно посмотреть на github:

===
Перепечатка материалов блога разрешается с обязательной ссылкой на blog.coolsoftware.ru