Quest: WordPress – wtyczki

Rozbuduj funkcjonalność Swojej strony. Dodaj:

  1. Podstronę na hasło
  2. Podstronę wymagającą zalogowania
  3. System gromadzenia statystyk
  4. Informację o ciasteczkach zgodną z prawem
  5. Działający formularz kontaktowy
  6. Efektowną podstronę zrobioną w Elementorze
  7. Dowolny skrypt JS (np. licznik czasu spędzonego przez gościa na stronie, zrobiony za pomocą AI)

Quest: Aplikacja bazodanowa cz.1

Opis aplikacji

Aplikacja ma być bazą informacji o sprzęcie IT używanym w firmie. Gromadzone informacje mają obejmować:

  • Szczegółowe informacje o parametrach sprzętu
  • Informacje o lokalizacji sprzętu w budynku
  • Podstawowe informacje o użytkownikach aplikacji i uprawnieniach
  • Historię operacji wykonywanych w aplikacji

Stara wersja aplikacji wyglądała tak:

Nowa wersja ma działać na tej samej bazie danych. Stara baza danych wygląda tak:

CREATE TABLE `urzadzenia` (
  `id` int(11) NOT NULL,
  `uwiw` text NOT NULL,
  `kategoria` text NOT NULL,
  `sala` text NOT NULL,
  `lpwsali` text NOT NULL,
  `model` text NOT NULL,
  `wyglad` text NOT NULL,
  `procesor` text NOT NULL,
  `ram` text NOT NULL,
  `plyta` text NOT NULL,
  `dysk` text NOT NULL,
  `przekatna` text NOT NULL,
  `mac` text NOT NULL,
  `licencje` text NOT NULL,
  `inne` text NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin2;

CREATE TABLE `uzytkownicy` (
  `id` int(9) NOT NULL AUTO_INCREMENT,
  `login` text NOT NULL,
  `imie` text NOT NULL,
  `nazwisko` text NOT NULL,
  `ranga` text NOT NULL,
  `has` text NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=latin2;

CREATE TABLE `wydarzenia` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `id_urzadzenia` int(11) NOT NULL,
  `data` date DEFAULT NULL,
  `typ_wydarzenia` text NOT NULL,
  `opis` text NOT NULL,
  `status` text NOT NULL,
  `ip` text,
  `user` text,
  `dataczas` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4243 DEFAULT CHARSET=latin2;

//Fragment przykładowych danych
INSERT INTO `urzadzenia` VALUES (1,'S_UW/IW/367','serwer','324 zapl','1','','Serwer Dell','Xeon','1GB','','','','','ws2003',''),(2,'ZZS S_UWIW 218','serwer','308z','1','','Serwer Dell','Xeon','1GB','','','','','ws2003',''),(3,'S_UW/IW/176','serwer','308z','2','','Serwer Składak','','','','2 hdd 160GB','','','',''),(4,'S_UWIW','serwer','308z','','MAC serwer Model A1186 EMC No: 2113','Srebrny MAC','','1GB?','','hdd 160GB?','','','OSX',''),(5,'UWIW 459,457,456','serwer','308z','3','NAS Qnap TS-219P?','NAS','','','','2 hdd 1TB','','','QOS',''),(6,'UW/IW/632','serwer','308z','4','NAS Qnap TS-669PRO','NAS','Intel Atom D2700 2130 MHz','1GB','','6 hdd 2TB','','','QOS',''),(7,'UW/IW/633','serwer','308z','5','HP ML310eGen8v2','','Xeon E3-1240v3','16GB','','2 hdd 2TB','','','ws2012 HyperVcore',''),(8,'UWIW/552','komputer','322','1','stacjonarny i5-4430 GA-B85M','Brutus','i5 4430 3GHz','8GB','Gigabyte GA-B85M-HD3','HDD 500GB wcc1u3907310','','94-DE-80-B5-77-92','w7hoem','Kupione z obudowami Logic teraz obudowy Brutus');)
INSERT INTO `wydarzenia` VALUES (1,1,'1980-01-01','zakup','data niepewna','potwierdzony','10.1.1.1','admi','2018-01-01 07:00:00'),(2,2,'1980-01-01','zakup','data niepewna','potwierdzony','10.1.1.1','admi','2018-01-01 07:00:00'),(3,3,'1980-01-01','zakup','data niepewna','potwierdzony','10.1.1.1','admi','2018-01-01 07:00:00'),(4,4,'2007-01-01','zakup','data niepewna','potwierdzony','10.1.1.1','admi','2018-01-01 07:00:00'),(5,5,'2012-01-01','zakup','','potwierdzony','10.1.1.1','admi','2018-01-01 07:00:00'),(6,6,'2015-01-01','zakup','','potwierdzony','10.1.1.1','admi','2018-01-01 07:00:00'),(7,7,'2015-01-01','zakup','','potwierdzony','10.1.1.1','admi','2018-01-01 07:00:00'),(8,8,'2013-09-01','zakup','','potwierdzony','10.1.1.1','admi','2018-01-01 07:00:00'),(9,9,'2013-09-01','zakup','','potwierdzony','10.1.1.1','admi','2018-01-01 07:00:00'),(10,10,'2013-09-01','zakup','','potwierdzony','10.1.1.1','admi','2018-01-01 07:00:00'),(11,11,'2013-09-01','zakup','','potwierdzony','10.1.1.1','admi','2018-01-01 07:00:00');)


Etap 1 – API

  • Utwórz instalator tworzący bazą danych wraz z danymi testowymi.
  • Zaprojektuj i napisz REST API w PHP. Dane przekazywać będziemy jako json. Wykonaj testy.

Etap 2 – Szkielet frontendu

Utwórz w HTML i CSS layout zgodny z layoutem starej wersji:

  • Wykorzystaj bibliotekę Bootstrap
  • Wypełnij statycznymi danymi
  • Zbuduj wygląd głównej tabeli (dynamiczne komponenty aplikacji utworzymy później). Użyj css grid w oparciu o Bootstrapa lub np. tak jak w tym queście
  • W nagłówku zaplanuj miejsce na komponent nawigacji i na komponent filtrów
  • W stopce zaplanuj miejsce na komponent statystyk/liczników
  • Zadbaj o responsywność

Etap 3 – Struktura aplikacji React

Na bazie przygotowanego szkieletu HTML utwórz strukturę aplikacji React (Vite):

  • plik index.html
  • plik src/index.css
  • plik src/main.jsx
  • plik src/App.jsx
  • plik src/App.css
  • Komponenty należy tworzyć w src/components. Nazwa pliku to nazwa komponentu – zawsze zaczyna się od dużej litery. Style css komponentu wstawiaj w osobnych plikach o tej samej nazwie.

Przenieś statyczne treści ze szkieletu do odpowiednio nazwanych komponentów. Podziel też css.

Etap 4 – połączanie z API

Główną tabelę aplikacji napełnij danymi z bazy danych poprzez API.


Quest: Node.js API z prostym frontendem React

Wstęp

Zadanie polega na zbudowaniu, uruchomieniu i przetestowaniu prostego CRUD API oraz uruchomieniu frontendu testowego w React. Załóżmy że chcemy przechowywać w bazie danych informacje o produktach (nazwa, opis, cena).
Użyjemy Node.js + Express.js + MySQL + mysql. Dane będziemy przekazywać i odbierać jako json.

Prosty serwer na Node

Zainstaluj wymagane zależności:

cd katalog
npm init -y
npm install express mysql cors

Utwórz bazę w MySQL (np w XAMPP przez PhpMyAdmin)
Stwórz i uruchom plik app.js z następującą zawartością:

const express = require('express');
const mysql = require('mysql');
const cors = require('cors');

// Tworzenie aplikacji Express
const app = express();

// Dzieki tej bibliotece będzie łatwiej. To takie obejście dla Same-Origin Policy w przeglądarce.
app.use(cors());

// Middleware do parsowania JSON (Express ma wbudowany parser JSON od wersji 4.16)
app.use(express.json());

// Konfiguracja połączenia z bazą danych
const db = mysql.createConnection({
    host: 'localhost',
    user: 'root', // Zmień na swojego użytkownika
    password: '', // Ustaw swoje hasło
    database: 'products_db', // Ustaw nazwę swojej bazy danych
});

// Połączenie z bazą danych
db.connect((err) => {
    if (err) {
        console.error('Błąd połączenia z bazą danych:', err.message);
        return;
    }
    console.log('Połączono z bazą danych MySQL.');
});

// Tworzenie tabeli w bazie danych (jeśli nie istnieje)
const createTableQuery = `
    CREATE TABLE IF NOT EXISTS products (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(255) NOT NULL,
        description TEXT,
        price DECIMAL(10, 2) NOT NULL
    );
`;
db.query(createTableQuery, (err) => {
    if (err) throw err;
    console.log('Tabela "products" została utworzona.');
});

// Endpointy API

// 1. Pobierz wszystkie produkty
app.get('/products', (req, res) => {
    const query = 'SELECT * FROM products';
    db.query(query, (err, results) => {
        if (err) {
            res.status(500).send(err.message);
            return;
        }
        res.json(results);
    });
});

// 2. Pobierz produkt po ID
app.get('/products/:id', (req, res) => {
    const query = 'SELECT * FROM products WHERE id = ?';
    db.query(query, [req.params.id], (err, results) => {
        if (err) {
            res.status(500).send(err.message);
            return;
        }
        if (results.length === 0) {
            res.status(404).send('Produkt nie został znaleziony.');
            return;
        }
        res.json(results[0]);
    });
});

// 3. Dodaj nowy produkt
app.post('/products', (req, res) => {
    const { name, description, price } = req.body;
    const query = 'INSERT INTO products (name, description, price) VALUES (?, ?, ?)';
    db.query(query, [name, description, price], (err, result) => {
        if (err) {
            res.status(500).send(err.message);
            return;
        }
        res.status(201).send({ id: result.insertId, name, description, price });
    });
});

// 4. Aktualizuj produkt
app.put('/products/:id', (req, res) => {
    const { name, description, price } = req.body;
    const query = 'UPDATE products SET name = ?, description = ?, price = ? WHERE id = ?';
    db.query(query, [name, description, price, req.params.id], (err, result) => {
        if (err) {
            res.status(500).send(err.message);
            return;
        }
        if (result.affectedRows === 0) {
            res.status(404).send('Produkt nie został znaleziony.');
            return;
        }
        res.send('Produkt został zaktualizowany.');
    });
});

// 5. Usuń produkt
app.delete('/products/:id', (req, res) => {
    const query = 'DELETE FROM products WHERE id = ?';
    db.query(query, [req.params.id], (err, result) => {
        if (err) {
            res.status(500).send(err.message);
            return;
        }
        if (result.affectedRows === 0) {
            res.status(404).send('Produkt nie został znaleziony.');
            return;
        }
        res.send('Produkt został usunięty.');
    });
});

// Uruchomienie serwera
const PORT = 3000;
app.listen(PORT, () => {
    console.log(`Serwer działa na porcie ${PORT}`);
});

Testowanie API

Użyj gotowych narzędzi do testowania API. Proponuję Postman. W wersji bezpłatnej, bez zakładania konta, można wykonać wszystkie testy. Aby na przykład dodać dane do bazy za pomocą Postman:

  • Wybierz metodę POST.
  • Wprowadź URL: http://localhost:3000/products.
  • Przejdź do zakładki Body, wybierz opcję raw i jako format wybierz JSON. Wklej:
    { "name": "Produkt A", "description": "Opis produktu A", "price": 99.99 }
  • Kliknij Send.

Frontend w React

Kolejny krok to uruchomienie aplikacji React wykorzystującej nasze API.
Najpierw trzeba przygotować środowisko:

cd /home/piotr/sand/react_frontend_4_simple_api
npm create vite@latest my-app --template react
cd my-app
npm install
npm install axios
npm install bootstrap
npm run dev

Skopiuj poniższy kod do src/App.jsx

import { useEffect, useState } from 'react';
import axios from 'axios';
import 'bootstrap/dist/css/bootstrap.min.css';

const API_URL = 'http://localhost:3000/products';

function App() {
  const [products, setProducts] = useState([]);
  const [name, setName] = useState('');
  const [description, setDescription] = useState('');
  const [price, setPrice] = useState('');

  useEffect(() => {
    fetchProducts();
  }, []);

  const fetchProducts = async () => {
    try {
      const response = await axios.get(API_URL);
      setProducts(response.data);
    } catch (error) {
      console.error('Błąd podczas pobierania produktów:', error);
    }
  };

  const addProduct = async () => {
    if (!name || !price) return;
    try {
      await axios.post(API_URL, { name, description, price });
      setName('');
      setDescription('');
      setPrice('');
      fetchProducts();
    } catch (error) {
      console.error('Błąd podczas dodawania produktu:', error);
    }
  };

  return (
    <div className="container mt-5">
      <h2 className="mb-4">Lista Produktów</h2>
      <ul className="list-group mb-4">
        {products.map((product) => (
          <li key={product.id} className="list-group-item">
            <strong>{product.name}</strong> - {product.description} - ${product.price}
          </li>
        ))}
      </ul>
      <h3>Dodaj Produkt</h3>
      <div className="mb-3">
        <input type="text" className="form-control" placeholder="Nazwa" value={name} onChange={(e) => setName(e.target.value)} />
      </div>
      <div className="mb-3">
        <input type="text" className="form-control" placeholder="Opis" value={description} onChange={(e) => setDescription(e.target.value)} />
      </div>
      <div className="mb-3">
        <input type="number" className="form-control" placeholder="Cena" value={price} onChange={(e) => setPrice(e.target.value)} />
      </div>
      <button className="btn btn-primary" onClick={addProduct}>Dodaj Produkt</button>
    </div>
  );
}

export default App;

Zadanie na 4 i 5

Dodaj kolejne endpointy i kolejną tabelę w bazie (np. z zakupami i sprzedażami).
Dodaj obsługę tych endpointów w apce reactowej (prostą).