Add fourth lecture projects

This commit is contained in:
Tomas Krejci 2026-06-15 18:09:37 +02:00
parent b84e183651
commit 8e5912fcf9
21 changed files with 4996 additions and 0 deletions

View File

@ -0,0 +1,6 @@
{
"endOfLine": "lf",
"useTabs": true,
"printWidth": 120,
"tabWidth": 4
}

View File

@ -0,0 +1,30 @@
### List all dinosaurs
GET http://localhost:3000/dinosaurs
### Get one dinosaur
GET http://localhost:3000/dinosaurs/1
### Create a dinosaur
POST http://localhost:3000/dinosaurs
Content-Type: application/json
{
"name": "Triceratops",
"description" : "Triceratops (neboli „třírohá tvář“) …",
"period": "křída",
"wikipediaAddress": "https://cs.wikipedia.org/wiki/Triceratops"
}
### Replace a dinosaur
PUT http://localhost:3000/dinosaurs/1
Content-Type: application/json
{
"name": "Tyrannosaurus",
"description": "Tyrannosaurus …",
"period": "křída",
"wikipediaAddress": "https://en.wikipedia.org/wiki/Tyrannosaurus"
}
### Delete a dinosaur
DELETE http://localhost:3000/dinosaurs/1

View File

@ -0,0 +1,24 @@
import js from "@eslint/js";
import globals from "globals";
import json from "@eslint/json";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
ignores: ["node_modules/**", "package-lock.json"],
},
{
files: ["**/*.{js,mjs,cjs}"],
plugins: { js },
extends: ["js/recommended"],
languageOptions: { globals: globals.node, ecmaVersion: 2025 },
rules: {
indent: ["error", "tab"],
"linebreak-style": ["error", "unix"],
"no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
quotes: ["error", "double"],
semi: ["error", "always"],
},
},
{ files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] },
]);

View File

@ -0,0 +1,136 @@
import express from "express";
// This array acts as temporary storage, so all changes are lost when the server restarts.
const DINOSAURS = [
{
id: 1,
name: "Tyrannosaurus",
description:
"Tyrannosaurus („tyranský ještěr“ odvozeno ze starořeckého τύραννος tyrannos = vládce, resp. krutovládce; v případě druhu T. rex pak latinského rex = král) byl jeden z největších masožravých dinosaurů (teropodů) a zároveň jedním z největších suchozemských predátorů všech dob.",
period: "křída",
wikipediaAddress: "https://cs.wikipedia.org/wiki/Tyrannosaurus",
},
{
id: 2,
name: "Stegosaurus",
description:
"Stegosaurus („zastřešený ještěr“) byl rod ptakopánvého dinosaura, který žil v období pozdní jury (asi před 150 miliony let) na území Severní Ameriky. Jeho pojmenování znamená „střechovitý či zastřešený ještěr“, toto označení dostal podle plochých desek, menší měl na krku a větší na hřbetě a přední části ocasu.",
period: "jura",
wikipediaAddress: "https://cs.wikipedia.org/wiki/Stegosaurus",
},
{
id: 3,
name: "Iguanodon",
description:
"Iguanodon (z latiny: „leguání zub“, český název je také iguanodon) je rod ornitopodního dinosaura, který se vývojově nacházel někde mezi prvními hbitými dvounohými hypsilofodonty a kachnozobými dinosaury, jimiž věk ornitopodů vrcholil.",
period: "křída",
wikipediaAddress: "https://cs.wikipedia.org/wiki/Iguanodon",
},
{
id: 4,
name: "Velociraptor",
description:
"Velociraptor byl rod poměrně malého teropodního dinosaura, patřícího mezi dravé dromeosauridy. Byl zástupcem skupiny velociraptorinů, menších „srpodrápých“ teropodů, obývajících severní kontinenty v období pozdní křídy.",
period: "křída",
wikipediaAddress: "https://cs.wikipedia.org/wiki/Velociraptor",
},
{
id: 5,
name: "Brontosaurus",
description:
"Brontosaurus (z řec. βροντή, brontē = hrom + σαυρος, sauros = ještěr: „hřmotný ještěr“ či „hromový ještěr“) byl rod velkého sauropodního dinosaura z čeledi Diplodocidae. Žil v období svrchní jury, asi před 155 až 152 miliony let na území západu Severní Ameriky.",
period: "jura",
wikipediaAddress: "https://cs.wikipedia.org/wiki/Brontosaurus_(dinosaurus)",
},
];
const app = express();
const port = Number(process.env.PORT ?? 3000);
app.use(express.json());
// Return the complete in-memory collection.
app.get("/dinosaurs", (_request, response) => {
response.json(DINOSAURS);
});
app.get("/dinosaurs/:id", (request, response) => {
const dinosaurId = Number(request.params.id);
const dinosaur = DINOSAURS.find((dinosaur) => dinosaur.id === dinosaurId);
if (!dinosaur) {
return response.status(404).json({
message: "Dinosaur not found",
});
}
response.json(dinosaur);
});
app.post("/dinosaurs", (request, response) => {
const dinosaur = request.body;
if (!dinosaur.name || !dinosaur.period) {
return response.status(400).json({
message: "The name and period fields are required",
});
}
const existingDinosaurIds = DINOSAURS.map((existingDinosaur) => existingDinosaur.id);
const newDinosaurId = Math.max(0, ...existingDinosaurIds) + 1;
const newDinosaur = {
...dinosaur,
id: newDinosaurId,
};
DINOSAURS.push(newDinosaur);
response.status(201).json(newDinosaur);
});
app.put("/dinosaurs/:id", (request, response) => {
const dinosaurId = Number(request.params.id);
const dinosaurBody = request.body;
if (!dinosaurBody.name || !dinosaurBody.period) {
return response.status(400).json({
message: "The name and period fields are required",
});
}
const dinosaurIndex = DINOSAURS.findIndex((dinosaur) => dinosaur.id === dinosaurId);
if (dinosaurIndex === -1) {
return response.status(404).json({
message: "Dinosaur not found",
});
}
DINOSAURS[dinosaurIndex] = {
...dinosaurBody,
id: dinosaurId,
};
response.status(204).send();
});
app.delete("/dinosaurs/:id", (request, response) => {
const dinosaurId = Number(request.params.id);
const dinosaurIndex = DINOSAURS.findIndex((dinosaur) => dinosaur.id === dinosaurId);
if (dinosaurIndex === -1) {
return response.status(404).json({
message: "Dinosaur not found",
});
}
DINOSAURS.splice(dinosaurIndex, 1);
response.status(204).send();
});
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
{
"name": "dinosaurs-without-db",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node index.js",
"lint": "eslint .",
"format": "prettier --write .",
"check": "npm run lint && prettier --check ."
},
"dependencies": {
"express": "^5.1.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@eslint/json": "^1.2.0",
"eslint": "^10.0.0",
"globals": "^17.4.0",
"prettier": "^3.0.0"
}
}

View File

@ -0,0 +1,5 @@
PGHOST="localhost"
PGPORT="5432"
PGDATABASE="rg_academy_dev"
PGUSER="rg_academy"
PGPASSWORD="rg_academy"

View File

@ -0,0 +1,6 @@
{
"endOfLine": "lf",
"useTabs": true,
"printWidth": 120,
"tabWidth": 4
}

View File

@ -0,0 +1,7 @@
import "dotenv/config";
import { Client } from "pg";
// The pg client reads PGHOST, PGPORT, PGDATABASE, PGUSER, and PGPASSWORD from the environment.
const client = new Client();
export default client;

View File

@ -0,0 +1,25 @@
import client from "./db-client.js";
import prompt from "prompt-sync";
const promptSync = prompt();
try {
await client.connect();
const departmentName = promptSync("Enter the name of the department: ").trim();
if (!departmentName) {
throw new Error("Department name cannot be empty.");
}
// Parameterized queries keep user input separate from SQL.
const result = await client.query("INSERT INTO department (name) VALUES ($1) RETURNING id, name", [departmentName]);
const newDepartment = result.rows[0];
console.log(`New department created with ID ${newDepartment.id}: ${newDepartment.name}`);
} catch (error) {
console.error(error.message);
process.exitCode = 1;
} finally {
await client.end();
}

View File

@ -0,0 +1,19 @@
import client from "./db-client.js";
try {
await client.connect();
const result = await client.query("SELECT id, name FROM department ORDER BY name");
if (result.rows.length === 0) {
console.log("No departments found.");
}
result.rows.forEach((departmentRow) => {
console.log(`${departmentRow.id}: ${departmentRow.name}`);
});
} catch (error) {
console.error(error.message);
process.exitCode = 1;
} finally {
await client.end();
}

View File

@ -0,0 +1,40 @@
import client from "./db-client.js";
import prompt from "prompt-sync";
const promptSync = prompt();
try {
await client.connect();
const id = Number(promptSync("Enter the ID of the department you want to update: "));
const newName = promptSync("Enter the new name of the department: ").trim();
if (!Number.isInteger(id) || id <= 0) {
throw new Error("Department ID must be a positive integer.");
}
if (!newName) {
throw new Error("Department name cannot be empty.");
}
// RETURNING avoids a separate query for checking whether the department exists.
const result = await client.query(
`UPDATE department
SET name = $1
WHERE id = $2
RETURNING id, name`,
[newName, id],
);
if (result.rows.length === 0) {
throw new Error(`Department with ID ${id} not found.`);
}
console.log("Updated department:");
console.table(result.rows[0]);
} catch (error) {
console.error(error.message);
process.exitCode = 1;
} finally {
await client.end();
}

View File

@ -0,0 +1,24 @@
import js from "@eslint/js";
import globals from "globals";
import json from "@eslint/json";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
ignores: ["node_modules/**", "package-lock.json"],
},
{
files: ["**/*.{js,mjs,cjs}"],
plugins: { js },
extends: ["js/recommended"],
languageOptions: { globals: globals.node, ecmaVersion: 2025 },
rules: {
indent: ["error", "tab"],
"linebreak-style": ["error", "unix"],
"no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
quotes: ["error", "double"],
semi: ["error", "always"],
},
},
{ files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] },
]);

1072
lecture_4/employees-cli/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
{
"name": "employees-cli",
"version": "1.0.0",
"type": "module",
"scripts": {
"departments:create": "node departments-create.js",
"departments:list": "node departments-list.js",
"departments:update": "node departments-update.js",
"lint": "eslint .",
"format": "prettier --write .",
"check": "npm run lint && prettier --check ."
},
"dependencies": {
"dotenv": "^17.4.2",
"pg": "^8.11.3",
"prompt-sync": "^4.2.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@eslint/json": "^1.2.0",
"eslint": "^10.0.0",
"globals": "^17.4.0",
"prettier": "^3.0.0"
}
}

View File

@ -0,0 +1,28 @@
-- Department table
CREATE TABLE department (
id SERIAL PRIMARY KEY,
name VARCHAR(256) NOT NULL UNIQUE
);
-- Employee table
CREATE TABLE employee (
id SERIAL PRIMARY KEY,
first_name VARCHAR(256) NOT NULL,
last_name VARCHAR(256) NOT NULL,
age INTEGER NOT NULL,
department_id INTEGER NOT NULL,
FOREIGN KEY (department_id) REFERENCES department(id)
);
-- Insert sample data into department table
INSERT INTO department (name) VALUES ('Vývoj');
INSERT INTO department (name) VALUES ('Obchod');
INSERT INTO department (name) VALUES ('Marketing');
-- Insert sample data into employee table
INSERT INTO employee (first_name, last_name, age, department_id)
VALUES ('Patrik', 'Moravec', 56, (SELECT id FROM department WHERE name = 'Obchod'));
INSERT INTO employee (first_name, last_name, age, department_id)
VALUES ('David', 'Sedláček', 29, (SELECT id FROM department WHERE name = 'Obchod'));
INSERT INTO employee (first_name, last_name, age, department_id)
VALUES ('Johana', 'Beranová', 52, (SELECT id FROM department WHERE name = 'Vývoj'));

View File

@ -0,0 +1,5 @@
{
"endOfLine": "lf",
"useTabs": true,
"printWidth": 120
}

View File

@ -0,0 +1,23 @@
import js from "@eslint/js";
import globals from "globals";
import json from "@eslint/json";
import { defineConfig } from "eslint/config";
export default defineConfig([
{
ignores: ["node_modules/**", "package-lock.json"],
},
{
files: ["**/*.{js,mjs,cjs}"],
plugins: { js },
extends: ["js/recommended"],
languageOptions: { globals: globals.node, ecmaVersion: 2025 },
rules: {
indent: ["error", "tab"],
"linebreak-style": ["error", "unix"],
quotes: ["error", "double"],
semi: ["error", "always"],
},
},
{ files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] },
]);

1796
lecture_5/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
lecture_5/package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "dinosaurs",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"dotenv": "^17.4.2",
"express": "^5.1.0",
"pg": "^8.11.3"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@eslint/json": "^1.2.0",
"eslint": "^10.0.0",
"globals": "^17.4.0",
"prettier": "^3.0.0"
}
}

45
lecture_5/schema.sql Normal file
View File

@ -0,0 +1,45 @@
-- Dinosaur table
CREATE TABLE dinosaur (
id SERIAL PRIMARY KEY,
name VARCHAR(256) NOT NULL,
description VARCHAR(4096) NOT NULL,
period VARCHAR(32) NOT NULL,
wikipedia_address VARCHAR(4096) NOT NULL
);
-- Insert sample data into dinosaur table
INSERT INTO dinosaur (name, description, period, wikipedia_address)
VALUES (
'Tyrannosaurus',
'Tyrannosaurus („tyranský ještěr" odvozeno ze starořeckého τύραννος tyrannos = vládce, resp. krutovládce; v případě druhu T. rex pak latinského rex = král) byl jeden z největších masožravých dinosaurů (teropodů) a zároveň jedním z největších suchozemských predátorů všech dob.',
'křída',
'https://cs.wikipedia.org/wiki/Tyrannosaurus'
);
INSERT INTO dinosaur (name, description, period, wikipedia_address)
VALUES (
'Stegosaurus',
'Stegosaurus („zastřešený ještěr") byl rod ptakopánvého dinosaura, který žil v období pozdní jury (asi před 150 miliony let) na území Severní Ameriky. Jeho pojmenování znamená „střechovitý či zastřešený ještěr", toto označení dostal podle plochých desek, menší měl na krku a větší na hřbetě a přední části ocasu.',
'jura',
'https://cs.wikipedia.org/wiki/Stegosaurus'
);
INSERT INTO dinosaur (name, description, period, wikipedia_address)
VALUES (
'Iguanodon',
'Iguanodon (z latiny: „leguání zub", český název je také iguanodon) je rod ornitopodního dinosaura, který se vývojově nacházel někde mezi prvními hbitými dvounohými hypsilofodonty a kachnozobými dinosaury, jimiž věk ornitopodů vrcholil.',
'křída',
'https://cs.wikipedia.org/wiki/Iguanodon'
);
INSERT INTO dinosaur (name, description, period, wikipedia_address)
VALUES (
'Velociraptor',
'Velociraptor byl rod poměrně malého teropodního dinosaura, patřícího mezi dravé dromeosauridy. Byl zástupcem skupiny velociraptorinů, menších „srpodrápých" teropodů, obývajících severní kontinenty v období pozdní křídy.',
'křída',
'https://cs.wikipedia.org/wiki/Velociraptor'
);
INSERT INTO dinosaur (name, description, period, wikipedia_address)
VALUES (
'Brontosaurus',
'Brontosaurus (z řec. βροντή, brontē = hrom + σαυρος, sauros = ještěr: „hřmotný ještěr" či „hromový ještěr") byl rod velkého sauropodního dinosaura z čeledi Diplodocidae. Žil v období svrchní jury, asi před 155 až 152 miliony let na území západu Severní Ameriky.',
'jura',
'https://cs.wikipedia.org/wiki/Brontosaurus_(dinosaurus)'
);