Dnes zdroják už v prevádzke preverenej pokec appky pre node.js

Dnes zdroják už v prevádzke preverenej pokec appky pre node.js

Okay, zatiaľ beží https://app.hrubos.tech/ dobre: https://hrubos.tech/repository/chat-app20.zip Toto obsluhuje chat app pre node.js:

// index.js
const express = require("express");
const multer = require("multer");
const path = require("path");
const fs = require("fs");

const app = express();
const PORT = process.env.PORT || 3000;
const MAX_MESSAGES = 100;

// Upload directory + JSON file
const uploadDir = path.join(__dirname, "uploads");
const dataFile = path.join(uploadDir, "data.json");
if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir, { recursive: true });
if (!fs.existsSync(dataFile)) fs.writeFileSync(dataFile, JSON.stringify([]));

// Multer storage pre obrázky
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 allowed = /jpeg|jpg|png|gif|webp|bmp/;
    const ext = allowed.test(path.extname(file.originalname).toLowerCase());
    const mime = allowed.test(file.mimetype);
    if (ext && mime) cb(null, true);
    else cb(
      new Error("Povolené súbory: JPG, PNG, GIF, WebP, BMP (max 5MB)")
    );
  }
}).single("image");

// Static files
app.use(express.static(path.join(__dirname, "client")));
app.use("/uploads", express.static(uploadDir));
app.use(express.json({ limit: "10mb" }));
app.use(express.urlencoded({ extended: true, limit: "10mb" }));

// Pomocné funkcie
function readData() {
  try { return JSON.parse(fs.readFileSync(dataFile)); }
  catch { return []; }
}

function writeData(data) {
  fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
}

// Automatické čistenie po prekročení MAX_MESSAGES
function cleanIfNeeded() {
  const data = readData();
  if (data.length > MAX_MESSAGES) {
    // vymaž všetky súbory okrem data.json
    fs.readdirSync(uploadDir).forEach(file => {
      if (file !== "data.json") fs.unlinkSync(path.join(uploadDir, file));
    });
    // vymaž data.json
    writeData([]);
    console.log("♻️  Vymazané všetky správy a obrázky po prekročení limitu");
  }
}

// Endpoint pre textové správy
app.post("/message", (req, res) => {
  const msg = {
    user: req.body.user || "Anon",
    text: req.body.text || "",
    image: null,
    time: new Date().toLocaleString()
  };

  const data = readData();
  data.push(msg);

  // Spočítať zostávajúce správy do premazania
  const remaining = MAX_MESSAGES - data.length;

  writeData(data);
  cleanIfNeeded();

  res.json({
    status: "ok",
    remaining: remaining > 0 ? remaining : 0,
    message:
      remaining > 0
        ? `Zostáva ${remaining} správ do premazania`
        : "Chat sa práve premazal"
  });
});

// Endpoint pre upload obrázka + správa
app.post("/upload", (req, res) => {
  upload(req, res, (err) => {
    if (err) return res.json({ error: err.message || "Chyba uploadu" });
    if (!req.file) return res.json({ error: "Žiadny súbor" });

    const msg = {
      user: req.body.user || "Anon",
      text: req.body.text || "",
      image: "/uploads/" + req.file.filename,
      time: new Date().toLocaleString()
    };

    const data = readData();
    data.push(msg);

    const remaining = MAX_MESSAGES - data.length;

    writeData(data);
    cleanIfNeeded();

    res.json({
      url: msg.image,
      remaining: remaining > 0 ? remaining : 0,
      message:
        remaining > 0
          ? `Zostáva ${remaining} správ do premazania`
          : "Chat sa práve premazal"
    });
  });
});

// Health endpoint
app.get("/health", (req, res) => {
  const data = readData();
  res.json({ status: "ok", messages: data.length });
});

// Spusti server
app.listen(PORT, () => {
  console.log(`🚀 Server beží na http://localhost:${PORT}`);
});
<!DOCTYPE html>
<html lang="sk">
<head>
<meta charset="UTF-8">
<title>Chat App</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { box-sizing:border-box; }
body {
  margin:0; padding:0; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
  display:flex; flex-direction:column; height:100vh; background: linear-gradient(135deg,#667eea 0%,#764ba2 100%);
}
#messages {
  list-style:none; padding:20px; margin:0; flex-grow:1; overflow-y:auto; display:flex; flex-direction:column; gap:12px;
}
#messages li {
  max-width:70%; padding:12px 16px; border-radius:18px; background:white; word-wrap:break-word;
  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
#messages li.me { align-self:flex-end; background:#2196F3; color:white; border-bottom-right-radius:4px; }
#messages li.system { align-self:center; background:#e0e0e0; color:#666; font-size:0.85em; font-style:italic; max-width:unset; }
#controls { display:flex; flex-wrap:wrap; align-items:center; padding:15px; background:rgba(255,255,255,0.95); gap:10px; }
#username,#input { flex:1; min-width:120px; padding:12px 16px; border-radius:25px; border:2px solid #e0e0e0; font-size:16px; }
#send,#fileLabel { padding:12px 20px; border-radius:25px; cursor:pointer; font-weight:500; border:none; }
#send { background:linear-gradient(45deg,#2196F3,#21CBF3); color:white; }
#fileLabel { background:linear-gradient(45deg,#4CAF50,#45a049); color:white; }
#fileInput { display:none; }
#errorMsg { color:#d32f2f; width:100%; text-align:center; font-size:14px; padding:10px; background:#ffebee; border-radius:8px; margin-top:5px; display:none; }
#loading { width:24px; height:24px; border:3px solid #f3f3f3; border-top:3px solid #2196F3; border-radius:50%; animation:spin 1s linear infinite; display:none; margin-left:10px; }
#imagePreview { max-width:80px; max-height:80px; border-radius:12px; box-shadow:0 2px 8px rgba(0,0,0,0.2); margin-left:10px; }
img { max-width:100%; height:auto; border-radius:12px; margin-top:8px; }
@keyframes spin { 0% { transform:rotate(0deg); } 100% { transform:rotate(360deg); } }
@media(max-width:600px){
  #controls{flex-direction:column; align-items:stretch; padding:10px;}
  #username,#input,#send,#fileLabel{width:100%; margin:5px 0;}
  #messages li{max-width:90%;}
  #loading,#imagePreview{margin-left:0;}
}
</style>
</head>
<body>

<ul id="messages"></ul>

<div id="controls">
  <input id="username" placeholder="Tvoja prezývka" maxlength="20">
  <input id="input" placeholder="Napíš správu..." autocomplete="off">
  <label id="fileLabel" for="fileInput">🖼️ Obrázok</label>
  <input type="file" id="fileInput" accept="image/*">
  <button id="send">➤ Pošli</button>
  <div id="loading"></div>
  <div id="imagePreview"></div>
  <div id="errorMsg"></div>
</div>

<script>
const messagesEl = document.getElementById("messages");
const input = document.getElementById("input");
const username = document.getElementById("username");
const send = document.getElementById("send");
const fileInput = document.getElementById("fileInput");
const errorMsg = document.getElementById("errorMsg");
const loading = document.getElementById("loading");
const imagePreview = document.getElementById("imagePreview");

// ENTER support
input.addEventListener("keydown", e => { if(e.key==="Enter") send.click(); });

// Mini náhľad obrázka
fileInput.addEventListener("change", ()=>{
  const file = fileInput.files[0];
  if(!file) { imagePreview.style.display="none"; return; }
  const reader = new FileReader();
  reader.onload = e => {
    imagePreview.innerHTML = `<img src="${e.target.result}">`;
    imagePreview.style.display="block";
  };
  reader.readAsDataURL(file);
});

// Systémová správa
function addSystemMessage(text){
  const li = document.createElement("li");
  li.className = "system";
  li.textContent = text;
  messagesEl.appendChild(li);
  messagesEl.scrollTop = messagesEl.scrollHeight;
}

// Zobraz chat správu
function displayMessage(msg){
  const li = document.createElement("li");
  li.className = (msg.user === (username.value||"Anon")) ? "me" : "";
  li.innerHTML = `<strong>${msg.user}:</strong> ${msg.text}`;
  if(msg.image){
    const img = document.createElement("img");
    img.src = msg.image;
    img.onerror = ()=>img.alt="Chyba pri načítaní obrázka";
    li.appendChild(document.createElement("br"));
    li.appendChild(img);
  }
  messagesEl.appendChild(li);
  messagesEl.scrollTop = messagesEl.scrollHeight;
}

// Chyba
function showError(msg){
  errorMsg.textContent = msg;
  errorMsg.style.display = "block";
  setTimeout(()=>{ errorMsg.style.display="none"; },5000);
}

// Načítanie všetkých správ z data.json
async function loadMessages(){
  try{
    const res = await fetch("/uploads/data.json");
    const data = await res.json();
    messagesEl.innerHTML="";
    data.forEach(displayMessage);
  } catch(e){
    console.error("Nepodarilo sa načítať správy:",e);
  }
}

// Odoslanie správy
send.onclick = async ()=>{
  const text = input.value.trim();
  const user = username.value.trim() || "Anon";
  if(!text && !fileInput.files[0]) return showError("Napíš správu alebo pridaj obrázok");
  send.disabled=true;
  loading.style.display="block";

  try{
    if(fileInput.files[0]){
      const fd = new FormData();
      fd.append("image", fileInput.files[0]);
      fd.append("user", user);
      fd.append("text", text);

      const res = await fetch("/upload",{method:"POST",body:fd});
      const data = await res.json();
      if(data.error) throw new Error(data.error);
      if(data.message) addSystemMessage(data.message);
      await loadMessages();
    } else {
      const res = await fetch("/message",{
        method:"POST",
        headers:{"Content-Type":"application/json"},
        body: JSON.stringify({user,text})
      });
      const data = await res.json();
      if(data.message) addSystemMessage(data.message);
      await loadMessages();
    }

    input.value="";
    fileInput.value="";
    imagePreview.style.display="none";

  } catch(e){ showError(e.message); }
  finally{ send.disabled=false; loading.style.display="none"; }
};

// REKURZÍVNY POLLING
async function pollMessages(){
  try {
    await loadMessages();
  } catch(e){
    console.error(e);
  } finally {
    setTimeout(pollMessages,9000);
  }
}

pollMessages();
</script>

</body>
</html>


Author: AarNoma

The first Slovak cyborg 1 system

Comments “Dnes zdroják už v prevádzke preverenej pokec appky pre node.js”