Blog Tinux Net

tinux

author_description

  • Publicado en

    Para crear un CRUD (Crear, Leer, Actualizar y Eliminar) de usuarios utilizando Node.js con Express como backend y MariaDB como base de datos, siguiendo tu configuración con Apache2 y un dominio virtual hay que seguir estos pasos:

    Configuración inicial
    Primero, asegurarse de tener instalados Node.js, npm y MariaDB en el sistema. Luego, crea un nuevo proyecto e instalar las dependencias necesarias:

    mkdir crud-usuarios
    cd crud-usuarios
    npm init -y
    npm install express mysql2 body-parser
    

    Estructura del proyecto
    Crear la siguiente estructura de archivos:

    crud-usuarios/
    ├── app.js
    ├── config/
    │ └────── db.js
    └── routes/
    └────── users.js

    Configuración de la base de datos
    En el archivo config/db.js, configura la conexión a MariaDB:
    [ config/db.js ]

    const mysql = require('mysql2');
    
    const connection = mysql.createConnection({
      host: 'localhost',
      user: 'tu_usuario',
      password: 'tu_contraseña',
      database: 'tu_base_de_datos'
    });
    
    connection.connect(error => {
      if (error) throw error;
      console.log('Conexión exitosa a la base de datos.');
    });
    
    module.exports = connection;
    

    Configuración del servidor Express
    En el archivo app.js, configura el servidor Express:

    [ app.js ]

    const express = require('express');
    const bodyParser = require('body-parser');
    const usersRoutes = require('./routes/users');
    
    const app = express();
    
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));
    
    app.use('/api/users', usersRoutes);
    
    const PORT = process.env.PORT || 3000;
    app.listen(PORT, () => {
      console.log(`Servidor corriendo en el puerto ${PORT}`);
    });
    

    Implementación de las rutas CRUD
    En el archivo routes/users.js, implementa las rutas para el CRUD:
    [ routes/users.js ]

    const express = require('express');
    const router = express.Router();
    const db = require('../config/db');
    
    // Crear un usuario
    router.post('/', (req, res) => {
      const { user, username } = req.body;
      const query = 'INSERT INTO users (user, username) VALUES (?, ?)';
    
      db.query(query, [user, username], (error, results) => {
        if (error) {
          return res.status(500).json({ error: error.message });
        }
        res.status(201).json({ id: results.insertId, user, username });
      });
    });
    
    // Obtener todos los usuarios
    router.get('/', (req, res) => {
      const query = 'SELECT * FROM users';
    
      db.query(query, (error, results) => {
        if (error) {
          return res.status(500).json({ error: error.message });
        }
        res.json(results);
      });
    });
    
    // Obtener un usuario por ID
    router.get('/:id', (req, res) => {
      const query = 'SELECT * FROM users WHERE id = ?';
    
      db.query(query, [req.params.id], (error, results) => {
        if (error) {
          return res.status(500).json({ error: error.message });
        }
        if (results.length === 0) {
          return res.status(404).json({ message: 'Usuario no encontrado' });
        }
        res.json(results[0]);
      });
    });
    
    // Actualizar un usuario
    router.put('/:id', (req, res) => {
      const { user, username } = req.body;
      const query = 'UPDATE users SET user = ?, username = ? WHERE id = ?';
    
      db.query(query, [user, username, req.params.id], (error, results) => {
        if (error) {
          return res.status(500).json({ error: error.message });
        }
        if (results.affectedRows === 0) {
          return res.status(404).json({ message: 'Usuario no encontrado' });
        }
        res.json({ id: req.params.id, user, username });
      });
    });
    
    // Eliminar un usuario
    router.delete('/:id', (req, res) => {
      const query = 'DELETE FROM users WHERE id = ?';
    
      db.query(query, [req.params.id], (error, results) => {
        if (error) {
          return res.status(500).json({ error: error.message });
        }
        if (results.affectedRows === 0) {
          return res.status(404).json({ message: 'Usuario no encontrado' });
        }
        res.json({ message: 'Usuario eliminado correctamente' });
      });
    });
    
    module.exports = router;
    

    Configuración de Apache2 como proxy inverso
    Para que Apache2 funcione como proxy inverso para la aplicación Node.js, se necesita configurar un virtual host. Editar el archivo de configuración del dominio virtual:

    sudo nvim  /etc/apache2/sites-available/subdomain.dominio.net.conf
    

    Añadir la siguiente configuración:

    <VirtualHost *:80>
        ServerName subdomain.dominio.net
        ProxyPreserveHost On
        ProxyPass / http://localhost:3000/
        ProxyPassReverse / http://localhost:3000/
        ErrorLog ${APACHE_LOG_DIR}/subdomain.dominio.net-error.log
        CustomLog ${APACHE_LOG_DIR}/subdomain.dominio.net-access.log combined
    </VirtualHost>
    

    Habilitar el módulo proxy de Apache2 y reiniciar el servicio:

    sudo a2enmod proxy proxy_http
    sudo systemctl restart apache2
    

    Uso del CRUD
    Ahora puedes usar el CRUD a través de tu dominio virtual:

    • Crear un usuario: POST http://subdomain.dominio.net/api/users
    • Obtener todos los usuarios: GET http://subdomain.dominio.net/api/users
    • Obtener un usuario por ID: GET http://subdomain.dominio.net/api/users/:id
    • Actualizar un usuario: PUT http://subdomain.dominio.net/api/users/:id
    • Eliminar un usuario: DELETE http://subdomain.dominio.net/api/users/:id

    Iniciar la aplicación Node.js con:

    node app.js
    

    Este CRUD te permitirá gestionar los usuarios en tu base de datos MariaDB a través de una API RESTful, accesible desde tu dominio virtual http://subdomain.dominio.tinux.net

  • Publicado en

    Actualizacion de Nextcloud

    #!/bin/zsh  
    
        # Definir la URL de la página de descargas de Nextcloud  
    
    URL="https://nextcloud.com/install/#install-clients"
    
    # Obtener el enlace de descarga de la última versión AppImage
    DOWNLOAD_URL=$(curl -s $URL | grep -oP 'https://.*?Nextcloud-.*?-x86_64\.AppImage' | head -n 1)
    
    if [ -z "$DOWNLOAD_URL" ]; then
        echo "No se pudo encontrar el enlace de descarga de la AppImage."
        exit 1
    fi
    
    # Descargar la AppImage
    echo "Descargando la última versión de Nextcloud..."
    wget -q "$DOWNLOAD_URL" -O /tmp/nextcloud.AppImage
    
    if [ $? -ne 0 ]; then
        echo "Error al descargar la AppImage."
        exit 1
    fi
    
        # Hacer ejecutable la AppImage
    
    chmod +x /tmp/nextcloud.AppImage
    
    # Detener el proceso de Nextcloud si está en ejecución
    pkill -f nextcloud
    
        # Reemplazar el archivo existente
    
    echo "Reemplazando el archivo existente..."
    sudo mv /tmp/nextcloud.AppImage /usr/bin/nextcloud
    
    if [ $? -ne 0 ]; then
        echo "Error al reemplazar el archivo. Asegúrate de tener permisos de superusuario."
        exit 1
    fi
    
    echo "Actualización completada con éxito."
    
  • Publicado en

    Al analizar la página web proporcionada, se pueden detectar algunos errores y áreas de mejora en su estructura HTML:

    Errores de estructura

    1. Falta de estructura HTML básica: No se observa la estructura HTML completa con las etiquetas <!DOCTYPE html>, <html>, <head> y <body>[1]. Esto es fundamental para una correcta interpretación por parte de los navegadores.

    2. Ausencia de metadatos: No se incluyen etiquetas meta importantes como <meta charset="UTF-8"> para la codificación de caracteres, ni <meta name="viewport"> para la adaptación a dispositivos móviles[1].

    3. Falta de título: No se encuentra la etiqueta <title> que debería estar dentro del <head> para definir el título de la página[1].

    Problemas de accesibilidad y semántica

    1. Uso incorrecto de encabezados: El texto "YOUR HOUSE UNDER THE SUN" debería ser un encabezado principal <h1> en lugar de texto plano[1].

    2. Falta de etiquetas semánticas: No se utilizan etiquetas como <header>, <nav>, <main>, <section> o <footer> para estructurar el contenido de manera semántica[1].

    3. Ausencia de atributos alt: Las imágenes de las propiedades carecen de atributos alt para describir su contenido, lo cual es esencial para la accesibilidad[1].

    Errores de contenido

    1. Inconsistencia en el idioma: El título está en inglés ("YOUR HOUSE UNDER THE SUN"), mientras que el resto del contenido está en español. Se debería mantener un idioma consistente o utilizar etiquetas lang apropiadas[1].

    2. Falta de formulario: El campo de búsqueda "Buscar" no está dentro de un elemento <form>, lo que podría afectar su funcionalidad[1].

    3. Información de contacto no estructurada: Los datos de contacto deberían estar dentro de una estructura más semántica, como una lista o utilizando la etiqueta <address>[1].

    Corregir estos errores mejoraría significativamente la estructura, accesibilidad y optimización para motores de búsqueda de la página web.

    Citations: [1] https://ppl-ai-file-upload.s3.amazonaws.com/web/direct-files/10537800/a394b72b-738b-4ab9-b0c8-4fc08fc4d20c/paste.txt

  • Publicado en

    Creado dos archivos, el script en s, y el archivo de exclusion

    File rsync-remote.zsh :

    rsync -avr --exclude-from='.exclude.rsync' --exclude  ~/NombreCliente/httpdocs-dev/ nombre_usuario@dominio.io:/ruta/absoluta/del/httpdocs-dev
    

    Recomendable es que se ponga la clave publica de .ssh en el directorio de know_host para que no se tenga que preguntar por contraseña File .exclude.rsync :

    .exclude.rsync
    node_modules/
    src/
    software/
    package-lock.json
    package.json
    *nextcloudsync.log
    *.db
    *.dba
    *.db-wal
    *sync-exclude.lst
    *db-shm
    

    Principalmente son los archivos que no son necesario o bien tendrían un problema de seguridad.

  • Publicado en

    Tabla de tamaños imagenes

    Tipo de Imagen Desktop Mobile Ratio
    Background 2560 x 1400 pixels 360 x 640 pixels 16:9
    Principal 1280 x 720 pixels 360 x 200 pixels 16:9
    Banner 1200 x 400 pixels 360 x 120 pixels 3:1
    Blog 1200 x 800 pixels 360 x 240 pixels 3:2
    Logo (rectangle) 400 x 100 pixels 160 x 40 pixels 4:1
    Logo (square) 100 x 100 pixels 60 x 60 pixels 1:1
    Favicon 16 x 16 pixels 16 x 16 pixels 1:1
    Íconos redes 32 x 32 pixels 48 x 48 pixels 1:1
    Lightbox (full screen) 1920 x 1080 pixels 360 x 640 pixels 16:9
    Thumbnail 300 x 300 pixels 90 x 90 pixels 1:1
  • Publicado en

    Archivo index.php:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Autocomplete by MRoblesDev</title>
    
        <style type="text/css">
            ul {
                list-style-type: none;
                width: 300px;
                height: auto;
                position: absolute;
                margin-top: 10px;
                margin-left: 10px;
            }
    
            li {
                background-color: #EEEEEE;
                border-top: 1px solid #9e9e9e;
                padding: 5px;
                width: 100%;
                float: left;
                cursor: pointer;
            }
        </style>
    </head>
    
    <body>
        <h3>Autocomplete</h3>
    
        <form action="" method="post" autocomplete="off">
            <div>
                <label for="campo">Buscar:</label>
                <input type="text" name="campo" id="campo">
    
                <ul id="lista"></ul>
            </div>
        </form>
    
        <script src="js/peticiones.js"></script>
    </body>
    
    </html>
    

    Archivo .env para los datos de acceso de la database. Se puede crear un directoro inc para su inclusion:
    database.env

    <?php
    
    class Database
    {
        private $hostname = "localhost";
        private $database = "database";
        private $username = "user";
        private $password = "password";
        private $charset = "utf8";
    
        /**
         * Se conecta a la base de datos y devuelve un objeto PDO. (PHP Data Objects) 
         * 
         * @return La conexión a la base de datos.
         */
        function conectar()
        {
            try {
                $conexion = "mysql:host=" . $this->hostname . ";dbname=" . $this->database . ";charset=" . $this->charset;
                $options = [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_EMULATE_PREPARES => false,
                ];
    
                $pdo = new PDO($conexion, $this->username, $this->password, $options);
    
                return $pdo;
            } catch (PDOException $e) {
                echo 'Error conexion: ' . $e->getMessage();
                exit;
            }
        }
    }
    

    El archivo de funciones de codigo de php:

    <?php
    
    require 'database.php';
    
    $con = new Database();
    $pdo = $con->conectar();
    
    $campo = $_POST["campo"];
    
    $sql = "SELECT cp, asentamiento FROM codigos_postales WHERE cp LIKE ? OR asentamiento LIKE ? ORDER BY cp ASC LIMIT 0, 10";
    $query = $pdo->prepare($sql);
    $query->execute([$campo . '%', $campo . '%']);
    
    $html = "";
    
    while ($row = $query->fetch(PDO::FETCH_ASSOC)) {
        $html .= "<li onclick=\"mostrar('" . $row["cp"] . "')\">" . $row["cp"] . " - " . $row["asentamiento"] . "</li>";
    }
    
    echo json_encode($html, JSON_UNESCAPED_UNICODE);
    

    Archivo de sql para inyectar en la base de datos para hacer las pruebas:

    Pongo el enolace, ya que es muy extenso:

    Database SQl Archive

  • Publicado en

    Estos son los principales Meta Tags de OpenGraph. Son los principales para que aparezcan cuando se comparte por Whtasapp , Telegram o Signal.

    <meta name='og:title' content='Tinux Net'>
    <meta name='og:type' content='image'>
    <meta name='og:url' content='http://www.imdb.com/title/tt0117500/'>
    <meta name='og:image' content='http://ia.media-imdb.com/rock.jpg'>
    <meta name='og:site_name' content='IMDb'>
    <meta name='og:description' content='Tinux Net, En la red desde 1997 ..'>
    

    Y si hay video recomendable poner.

    <meta property='og:video:type' content='application/x-shockwave-flash'>
    <meta property='og:video' content='http://example.com/html5.mp4'>
    <meta property='og:video:type' content='video/mp4'>
    <meta property='og:video:height' content='640'> 
    <meta property='og:video:width' content='385'>
    <meta property='og:video' content='http://example.com/fallback.vid'>
    <meta property='og:video:type' content='text/html'>
    
  • Publicado en

    index.html

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>Simple AJAX - GET REQUEST</title>
      </head>
      <body>
    
        <button onclick="sendRequest()">
    

    Hace la llamada a la funcion javascript del archivo script.js

          Send Ajax Request
        </button>
    
        <div id="container"></div>
    <script src="script.js" type="text/javascript"></script>
      </body>
    </html>
    

    script.js:

    function sendRequest() {
    

    Creamos el Objeto que va a recibir los datos a través de AJAX

        var theObject = new XMLHttpRequest();
    

    Verificamos que se ha enviado una petición

        theObject.open('POST', 'backend.php', true);
    

    Indicamos que tipo de cabecera se ha enviado a través del navegador

        theObject.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    

    Vemos en cual es el State o Estado

        theObject.onreadystatechange = function() {
          if(theObject.readyState === 4 & theObject.status === 200) {
    

    Si el Estado es Ok =200 y Enviado procedemos a insertar en el div con id container el Texto del Objeto

            document.getElementById('container').innerHTML = theObject.responseText;
          }
        }
    

    Y lo enviamos

        theObject.send('username=Fazt&password=secret');
      }
    

    Y el fichero que se ejecutara en el Backend: backend.php:

    <?php
    
    if(isset($_POST)) {
      echo $_POST['username'];
      echo '<br>';
      echo $_POST['password'];
    }
    
      #echo 'Working!';
    
    ?>
    
  • Publicado en

    Autocompletar un formulario con PHP, Javascript y MySQL

    Creamos 3 archivos: index.html, default.css y default.js

    Invocamos el css en la etiqueta con

    <link rel=""steelshhet" href="default.css">
    

    y el script de javascritp antes del final del </body>

    <script src="default.js"></script>
    

    En body del html

    <form action ="#" autocomplete=off" method="POST"> // Para que se limpie el input de varios valores anteriores 
         <div class="autocompletar">
             <input type="text" name=tipo-mascota" id="tipo-mascota" placeholder="Elige tu tipo de mascota">
         </div>
     </form>
    

    Aplicamos estilos en default.css

    body{
    font-famuly: Arial, sans-serif;
    }
    
    form{
    margin: 50px auto;
    widt: 500px;
    }
    
    .autocompletar{
    position: relative:
    display: inline-block;
    }
    strong {
    color: black;
    }
    
    
    #tipo-mascota{
    background-color #eee;
    border: none;
    border-radius: 30px;
    font-size: 1.7em;
    padding: 10px 25px;
    outline: none;
    width: 500px;
    }
    

    El estilo de salida de la lista de autocompletado

    .lista-autocompletar-items{
    color: ccc:
    position: abolute
    border: 1px solid #d4d4d4:
    border-bottom: none;
    z-index: 99;
    
    top: 100%;
    left: 20px;
    right: 0;
    width: 93%;
    }
    

    Utilizaremos el mismo identificador anterior pero le incorporaremos una capa.

    .lista-autocompletar-items div{
    padding: 10px;
    cursor: pointer;
    çbackground-color: #fff;
    border-bottom: 1px solid #d4d4d4;
    }
    

    y incorporaremos una capa para el hover:

           .lista-autocompletar-items div:hover{
    background-color: dogedeblue;
    }
    
    .autocompletar-active{
    backgound-color: doggerblue;
    color: #a1caff;
    }
    
    
    .autocompletar-active strong{
    
    color: white:
    }
    

    Una vez definido tambien el CSS vamos con el javascript default.js

    autocompletar(['perro','gato', 'pez','paloma','conejo']);
    
    function autocompletar(arreglo){
    const inputMascota =  document.querrySelector('#tipo-mascota');
    let indexFocus = -1;
    
    inputMadscota.addEventListener('input', function() {
    const tipomascota = this.value;
    if(!tipoMascota) return false;
    
    // Crear Lista de sugerencias
    cons divList = document.createElement('div');
    divList.setAttribute('id', this.id + );
    divList.setAttrribute('class');
    
    )
    });
    
    inputMascota.addEventListerner('keydown', function() {
    
    }
    
  • Publicado en

    Autocompletado Input

    Un sencillo input para un formulario.

    input.html:

    La clase del div es para poner los colores del background , la posicion general del div y su tipo de letra,

    Los estilos style.css:

    body {
      background-color: blue;
      font-family: sans-serif;
    }
    
    .autocomplete {
      display: flex;
      flex-direction: column;
      min-height: 100vh;
      align-items: center;
      justify-content: top;
      padding-top: 100px;
    }
    
    input {
      font-size: 2em;
      width: 300px;
    }
    
    .autocomplete-results {
      display: none; // Se indica None, para que no se muestre, luego con el javascript lo habilitaremos
      width: 300px;
      margin-top: -2px;
      font-size: 1.3em;
      color: #eee;
      list-style: none;
      margin: 0;
      padding: 0;
    }
    
      .autocomplete-results li {
        margin: 0;
        padding: 0;
        padding: 10px 0 10px 10px;
        border-right: 2px solid #eee;
        border-bottom: 2px solid #eee;
        border-left: 2px solid #eee;
      }
    
      .autocomplete-results li:hover {
        background-color: #eee;
        color: blue;
        cursor: pointer;
      }
    

    Y ahora el javascript: script.js:

    let input = document.querySelector("input");
    let autocomplete_results = document.getElementById('autocomplete-results');
    
    input.addEventListener('keyup',  (event) =>  {
      autocomplete_results.style.display = 'block';
      let key = event.target.value;
    
      console.log(key)
    
      if(key.length > 0) {
        search(key);
      }
      else {
        build_list();
      }
    })
    
    const search = (key) => {
      fetch(`https://restcountries.eu/rest/v2/name/${key}?fields=name`)
      .then(res => res.json())
      .then(data => {
        if(Array.isArray(data)) {
          build_list(data.map(item => {
            return item.name
          }))
        }
      })
    }
    
    const build_list = (items) => {
      console.log('elems', items)
    
      if(items === undefined) {
        items = [];
      }
    
      autocomplete_results.innerHTML = '';
    
      items.slice(0,10).map(item =>  {
        autocomplete_results.innerHTML += `<li>${item}</li>`;
                          })
    }
    
    document
      .getElementById("autocomplete-results")
      .addEventListener("click", e => {
        if (e.target && e.target.nodeName == "LI") {
    
          input.value = e.target.innerHTML;
          build_list()
        }
      });