Минималистичный счётчик трезвости

drunk.pw — минималистичный счётчик трезвости

drunk.pw — это простой, автономный и немного ироничный проект, показывающий, сколько дней ты держишься трезвым. Ночь поздняя сейчас например, дабы себя занять расскажу, как он устроен.

Работает на Express, хранит данные в JSON-файле, фронтенд оформлен в духе «панк-минимализма» наверное я так думаю.

Как устроено

  • Фронтенд: HTML + CSS + JavaScript
  • Бэкенд: Node.js + Express
  • Данные: обычный data.json
  • Запуск: через pm2
  • Веб Сервер: nginx

Структура

/var/www/drunk.pw/
├── index.html
├── server.js
├── data.json
└── package.json

Backend: server.js

Express. Обрабатывает /data, читает и пишет JSON-файл:

const express = require("express");
const fs = require("fs");
const path = require("path");
const app = express();
const PORT = 3000;

const dataFile = path.join(__dirname, "data.json");

app.use(express.static(__dirname));
app.use(express.json());

app.get("/data", (req, res) => {
  try {
    const data = fs.readFileSync(dataFile);
    res.setHeader("Content-Type", "application/json");
    res.send(data);
  } catch (err) {
    res.status(500).json({ error: "Data read error" });
  }
});

app.post("/data", (req, res) => {
  try {
    fs.writeFileSync(dataFile, JSON.stringify(req.body, null, 2));
    res.status(200).json({ status: "OK" });
  } catch (err) {
    res.status(500).json({ error: "Data write error" });
  }
});

app.listen(PORT, () => {
  console.log(`Drunk server listening on port ${PORT}`);
});

Фронт: index.html (фрагмент)

Вся логика счётчика реализована прямо в браузере. Пример:

<body>
  <h1>Live Fast, Die Last</h1>
  <div id="daysCounter">0 дней</div>
  <div id="lastRecord">0 дней</div>
  <button onclick="requestReset()">Выпить</button>

  <script>
    const hashedPassword = "fc0bee91..."; // SHA-256 хэш
    async function getData() {
      const res = await fetch("/data");
      const data = await res.json();
      const diff = Math.floor((new Date() - new Date(data.soberStart)) / (1000*60*60*24));
      document.getElementById("daysCounter").textContent = diff + " дней";
      document.getElementById("lastRecord").textContent = data.lastRecord + " дней";
    }
    async function requestReset() {
      const pass = prompt("Пароль:");
      const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(pass));
      const hex = [...new Uint8Array(hash)].map(b => b.toString(16).padStart(2, "0")).join("");
      if (hex === hashedPassword) resetCounter(); else alert("Хуй там!");
    }
    async function resetCounter() {
      const res = await fetch("/data");
      const data = await res.json();
      const days = Math.floor((new Date() - new Date(data.soberStart)) / (1000*60*60*24));
      data.records.push({ days, start: data.soberStart });
      data.lastRecord = days;
      data.soberStart = new Date().toISOString();
      await fetch("/data", {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: JSON.stringify(data)
      });
      getData();
    }
    getData();
  </script>
</body>

Установка и запуск

# Установка Node.js и менеджера процессов pm2
sudo apt update
sudo apt install nodejs npm -y
npm install -g pm2

# Развёртывание
cd /файлы
npm install
pm2 start server.js --name yourname
pm2 save
pm2 startup

Публикация

Ну через nginx конфиг. Проксируем локалхост:вашпорт

Заключение

drunk.pw — сделано по фану например.

Можно адаптировать под счётчик:

  • курения
  • дней без сахара
  • дней до отпуска

drunk.pw — no DB, no ads, no bullshit.