Ták náš nový app chat pokec je hotový, už len skúšajte a kecajte: https://app.hrubos.tech/

Ták náš nový app chat pokec je hotový, už len skúšajte a kecajte: https://app.hrubos.tech/

https://hrubos.tech/blogy/content/images/20260314074000-appchat1.png

Ták náš nový facebook type chat pokec je hotový. Už len skúšajte a kecajte: https://app.hrubos.tech/

https://hrubos.tech/blogy/content/images/20260314074148-appchat2.jpg app chat

Obsluhuje Vás jednoduchý JS skriptík do 200 riadkov na backende:

const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const multer = require("multer");
const path = require("path");
const fs = require("fs");
const rateLimit = require("express-rate-limit");
const cors = require("cors");

const app = express();
const server = http.createServer(app);
const io = new Server(server, {
  cors: {
    origin: ["http://localhost:3000", "http://127.0.0.1:3000"],
    credentials: true
  }
});

const PORT = process.env.PORT || 3000;
const MAX_HISTORY = 100;
let messagesHistory = [];

const uploadDir = path.join(__dirname, "uploads");
if (!fs.existsSync(uploadDir)) {
  fs.mkdirSync(uploadDir, { recursive: true, mode: 0o775 });
}

// ✅ FILE TYPE VALIDATION + RATE LIMITING
const storage = multer.diskStorage({
  destination: (req, file, cb) => cb(null, uploadDir),
  filename: (req, file, cb) => cb(null, Date.now() + "_" + Math.random().toString(36).substr(2, 9) + path.extname(file.originalname))
});

const upload = multer({ 
  storage, 
  limits: { fileSize: 5 * 1024 * 1024 },
  fileFilter: (req, file, cb) => {
    const allowedTypes = /jpeg|jpg|png|gif|webp|bmp/;
    const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
    const mimetype = allowedTypes.test(file.mimetype);

    if (extname && mimetype) {
      cb(null, true);
    } else {
      cb(new Error("Povolené súbor: JPG, PNG, GIF, WebP (max 5MB)"), false);
    }
  }
}).single("image");

// Rate limiting
const uploadLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minúta
  max: 5, // max 5 uploads za minútu
  message: { error: "Príliš veľa uploadov, skús neskôr!" },
  standardHeaders: true,
  legacyHeaders: false,
});

const messageLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: 20,
  message: { error: "Príliš veľa správ, spomaľ!" }
});

// Routes
app.get("/", (req, res) => res.sendFile(path.join(__dirname, "client", "index.html")));
app.get("/reset", (req, res) => { 
  messagesHistory = []; 
  fs.readdirSync(uploadDir).forEach(file => fs.unlinkSync(path.join(uploadDir, file)));
  res.send("✅ Reset OK"); 
});
app.get("/health", (req, res) => res.json({ status: "OK", messages: messagesHistory.length }));

// Middleware
app.use(cors({
  origin: ["http://localhost:3000", "http://127.0.0.1:3000"],
  credentials: true
}));
app.use(express.json({ limit: "10mb" }));
app.use(express.urlencoded({ extended: true, limit: "10mb" }));
app.use(express.static(path.join(__dirname, "client")));
app.use("/uploads", express.static(uploadDir));

// Upload endpoint s rate limiting
app.post("/upload", uploadLimiter, (req, res) => {
  console.log("📤 Upload request");

  upload(req, res, (err) => {
    if (err) {
      console.error("❌ Upload error:", err.message);
      if (err.code === "LIMIT_FILE_SIZE") {
        return res.status(400).json({ error: "Súbor je príliš veľký (max 5MB)" });
      }
      return res.status(500).json({ error: err.message || "Chyba pri nahrávaní" });
    }

    if (!req.file) {
      return res.status(400).json({ error: "Žiadny obrázok!" });
    }

    console.log("✅ Nahral sa:", req.file.filename);
    res.json({ url: `/uploads/${req.file.filename}` });
  });
});

// Socket.IO s pokročilým rate limiting
io.on("connection", (socket) => {
  console.log("👤 Pripojený:", socket.id);

  // Poslať históriu
  socket.emit("history", messagesHistory.slice(-50)); // posledných 50 správ

  // Rate limiting pre jednotlivých klientov
  socket.use((event, next) => {
    if (event[0] === "chat message") {
      const now = Date.now();
      const clientMessages = socket.data.messages || [];
      clientMessages.push(now);
      socket.data.messages = clientMessages.filter(time => now - time < 5000); // 5s okno

      if (socket.data.messages.length > 10) {
        return next(new Error("Príliš rýchlo píšeš!"));
      }
    }
    next();
  });

  socket.on("chat message", (data) => {
    // Validácia
    if (!data || typeof data.text !== "string") {
      socket.emit("error", "Neplatná správa");
      return;
    }

    if (data.text.length > 1000) {
      socket.emit("error", "Správa je príliš dlhá (max 1000 znakov)");
      return;
    }

    // XSS ochrana
    function escapeHTML(str) {
      const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
      return str.replace(/[&<>"']/g, m => map[m]);
    }

    const safeMsg = {
      user: escapeHTML(data.user?.slice(0, 20) || "Anon"),
      text: escapeHTML(data.text),
      image: data.image ? escapeHTML(data.image) : null,
      time: new Date().toLocaleString("sk-SK", { 
        day: "2-digit", 
        month: "2-digit", 
        year: "numeric", 
        hour: "2-digit", 
        minute: "2-digit" 
      })
    };

    // Uložiť do histórie
    messagesHistory.push(safeMsg);
    if (messagesHistory.length > MAX_HISTORY) {
      messagesHistory.shift();
    }

    // Poslať všetkým
    io.emit("chat message", safeMsg);
    console.log("💬", safeMsg.user, ":", safeMsg.text?.slice(0, 50));
  });

  socket.on("disconnect", () => {
    console.log("👋 Odpojil sa:", socket.id);
  });
});

// Graceful shutdown
function gracefulShutdown() {
  console.log("\n🛑 Ukončujem server...");
  io.close(() => {
    server.close(() => {
      console.log("✅ Server ukončený");
      process.exit(0);
    });
  });
}

process.on("SIGTERM", gracefulShutdown);
process.on("SIGINT", gracefulShutdown);

server.listen(PORT, () => {
  console.log(`🚀 Chat server beží na http://localhost:${PORT}`);
  console.log(`🔧 Health: http://localhost:${PORT}/health`);
  console.log(`🔄 Reset: http://localhost:${PORT}/reset`);
});

Na front ende: Viďte zdroják HTML5 stránky ;)


Author: AarNoma

The first Slovak cyborg 1 system

Comments “Ták náš nový app chat pokec je hotový, už len skúšajte a kecajte: https://app.hrubos.tech/”