This commit is contained in:
Andrew 2022-03-14 12:05:26 +07:00
parent a101924587
commit f662d9933f
13 changed files with 2691 additions and 0 deletions

3
w6/.dockerignore Normal file
View file

@ -0,0 +1,3 @@
node_modules
Dockerfile
.dockerignore

16
w6/Dockerfile Normal file
View file

@ -0,0 +1,16 @@
FROM node:16.13.2-alpine3.14
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
WORKDIR /home/node/app
COPY --chown=node:node . .
RUN corepack enable
RUN corepack prepare pnpm@6.32.2 --activate
RUN pnpm install
USER node
EXPOSE 9559/tcp
CMD [ "pnpm", "start" ]

1097
w6/goods.json Normal file

File diff suppressed because it is too large Load diff

79
w6/index.js Normal file
View file

@ -0,0 +1,79 @@
import express from "express";
import mustache from "mustache";
import bodyParse from "body-parser";
import { readdirSync, readFileSync } from "fs";
const app = express();
app.use(express.static("./static"));
app.use(bodyParse.urlencoded({ extended: true }));
const port = 9559;
const data = JSON.parse(readFileSync("./goods.json", "utf-8"));
const templates = new Map(readdirSync("./templates").map(fn => [fn, readFileSync(`./templates/${fn}`, "utf-8")]));
app.get("/", (req, res) => {
res.send(mustache.render(templates.get("mainPage.html"), data));
});
app.get("/category/:category", (req, res) => {
const goodsForCategory = data.goods.filter(el => el.category === req.params.category);
if (goodsForCategory.length === 0) {
res.redirect("/");
} else {
res.send(mustache.render(templates.get("categoryPage.html"), { category: req.params.category, goods: goodsForCategory }));
}
});
app.get("/goods/:goodUUID", (req, res) => {
let good = null;
let user = null;
for (let _good of data.goods) {
if (_good.guid !== req.params.goodUUID) continue;
good = _good;
break;
}
for (let _user of data.users) {
if (_user.id !== good.uid) continue;
user = _user;
break;
}
if (good === null || user === null) {
res.redirect("/");
} else {
res.send(mustache.render(templates.get("goodsPage.html"), { good, user }));
}
});
app.get("/user/:userUUID", (req, res) => {
let user = null;
for (let _user of data.users) {
if (_user.guid !== req.params.userUUID) continue;
user = _user;
break;
}
if (user === null) {
res.redirect("/");
} else {
const goods = data.goods.filter(e => e.uid === user.id);
res.send(mustache.render(templates.get("userPage.html"), { user, goods, goodsCount: goods.length }));
}
});
app.post("/search", (req, res) => {
const term = req.body.searchTerm.toLowerCase();
const st = req.body.searchType;
if (term === "") {
res.redirect("/");
} else {
const showUsers = st === "usersOnly" || st === "both";
const showGoods = st === "goodsOnly" || st === "both";
const users = showUsers? data.users.filter(u => u.fullName.toLowerCase().includes(term)) : [];
const goods = showGoods? data.goods.filter(e => e.title.toLowerCase().includes(term) || e.description.toLowerCase().includes(term)) : [];
res.send(mustache.render(templates.get("searchPage.html"), { term, users, goods, showUsers, showGoods }));
}
});
app.listen(port, () => console.log(`⚡️ Serving on port ${port}`));

22
w6/package.json Normal file
View file

@ -0,0 +1,22 @@
{
"name": "w6",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node .",
"watch": "nodemon -e *",
"build": "docker build -t nuark/w6_bld ."
},
"author": "nuark",
"license": "MIT",
"dependencies": {
"body-parser": "^1.19.2",
"express": "^4.17.3",
"mustache": "^4.2.0"
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}

1092
w6/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

154
w6/static/css/styles.css Normal file
View file

@ -0,0 +1,154 @@
* {
padding: 0;
margin: 0;
}
body {
display: flex;
flex-direction: column;
align-items: center;
background-color: bisque;
font-size: 16pt;
font-family: 'Smooch Sans', sans-serif;
}
a {
text-decoration: none;
color: darkturquoise;
}
a:visited {
color: none;
}
#mainPageMain {
display: flex;
flex-direction: column;
gap: 4rem;
width: 60%;
height: 100%;
padding: 2rem;
}
.jumbotronSection {
background-color: beige;
padding: 4rem;
border-radius: 2rem;
}
.searchSection {
background-color: beige;
padding: 1rem;
border-radius: 2rem;
}
.searchSection > form {
display: flex;
flex-direction: column;
}
.searchSection > form > input[type="text"] {
width: 100%;
height: 40px;
background-color: transparent;
border-top: none;
border-left: none;
border-right: none;
border-bottom: 2px black dashed;
margin-bottom: 2rem;
}
.categories {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.categoryCard {
display: flex;
flex: 1 0 40%;
flex-direction: row;
background-color: whitesmoke;
gap: 1rem;
margin: 1rem;
text-decoration: none;
color: black;
align-items: center
}
.categoryCard > img {
clip-path: circle(81% at 0% 50%);
}
.categoryCard > p {
padding-right: 4rem;
font-size: 28pt;
font-weight: 700;
}
.goodsSection {
background-color: whitesmoke;
padding: 2rem;
border-radius: 2rem;
}
.goods {
display: flex;
flex-direction: row;
flex-wrap: wrap;
row-gap: 2rem;
}
.goodsCard {
display: flex;
flex: 1 0 30%;
flex-direction: column;
background-color: whitesmoke;
gap: 1rem;
margin: 1rem;
align-items: center;
}
.goodsCard > .description {
height: 100%;
}
.goodsLink {
text-decoration: none;
color: coral;
padding: 5rem;
padding-top: 1rem;
padding-bottom: 1rem;
display: inline-block;
border: solid;
border-radius: 1rem;
border-color: coral;
border-width: 1px;
}
.userCard {
display: flex;
flex-direction: row;
align-items: center;
padding-top: 2rem;
padding-bottom: 2rem;
gap: 2rem;
}
.avatar {
height: 5rem;
border-radius: 50%;
}
.searchDataSection {
background-color: whitesmoke;
border-radius: 2rem;
padding: 2rem;
}
.goodsSearches {
display: flex;
flex-direction: row;
flex-wrap: wrap;
row-gap: 2rem;
}

View file

@ -0,0 +1,31 @@
<!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>Goods page</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Smooch+Sans:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<main id="mainPageMain">
<section class="goodsSection">
<a href="/">&gt;&gt;&gt; Go home</a>
<hr>
<h2>Goods in category "{{category}}":</h2>
<div class="goods">
{{#goods}}
<div class="goodsCard">
<h3>{{title}} # {{id}}</h3>
<p class="description">{{description}}</p>
<a class="goodsLink" href="/goods/{{guid}}">More info...</a>
</div>
{{/goods}}
</div>
</section>
</main>
</body>
</html>

View file

@ -0,0 +1,35 @@
<!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>"{{good.title}}" good page</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Smooch+Sans:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<main id="mainPageMain">
<section class="goodsSection">
<a href="/category/{{good.category}}">&gt;&gt;&gt; Go back to category "{{good.category}}"</a>
<hr>
<h2>Vieweing "{{good.title}}":</h2>
<p>In system since: {{good.registered}}</p>
<hr>
Seller profile: <a href="/user/{{user.guid}}">{{user.fullName}}</a>
<hr>
<h3>Description:</h3>
<p>{{good.description}}</p>
<br>
<h3>Price:</h3>
<p>{{good.price}} CR per unit</p>
</section>
</main>
</body>
</html>

View file

@ -0,0 +1,58 @@
<!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>Main page</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Smooch+Sans:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<main id="mainPageMain">
<section class="jumbotronSection">
<h1>Welcome to our store!</h1>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Neque labore perspiciatis ad reprehenderit magni, fugiat eligendi! Porro dignissimos rerum accusantium!</p>
</section>
<section class="searchSection">
<form action="/search" method="post">
<input type="text" name="searchTerm" placeholder="Search term">
<h4>Search for:</h4>
<div>
<label for="both">Users and goods</label>
<input type="radio" id="both" name="searchType" value="both" checked>
<br>
<label for="userOnly">Users only</label>
<input type="radio" id="userOnly" name="searchType" value="usersOnly">
<br>
<label for="goodsOnly">Goods only</label>
<input type="radio" id="goodsOnly" name="searchType" value="goodsOnly">
</div>
<br>
<button type="submit">Search!</button>
</form>
</section>
<section class="categoriesSection">
<h3>You can choose from a wide range of categories:</h3>
<div class="categories">
{{#categories}}
<a class="categoryCard" href="/category/{{.}}">
<img src="https://api.lorem.space/image?w=150&h=180&{{.}}" alt="{{.}}">
<p>
{{.}}
</p>
</a>
{{/categories}}
</div>
</section>
</main>
</body>
</html>

View file

@ -0,0 +1,62 @@
<!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>Search for "{{term}}"</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Smooch+Sans:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<main id="mainPageMain">
<section class="searchSection">
<h4>Search term: "{{term}}"</h4>
<h5>Searching for {{#showUsers}}users{{#showGoods}} and {{/showGoods}}{{/showUsers}}{{#showGoods}}goods{{/showGoods}}</h5>
<a href="/">&gt;&gt;&gt; Go home</a>
</section>
<section class="searchDataSection">
{{#showUsers}}
<h3>Found users:</h3>
<div class="categories">
{{#users}}
<a class="categoryCard" href="/user/{{guid}}">
<img src="{{photo}}&{{guid}}" alt="{{fullName}} photo">
<p>
{{fullName}}
</p>
</a>
{{/users}}
{{^users}}
No users found for this search!
{{/users}}
</div>
{{#showGoods}}
<br>
<hr>
<br>
{{/showGoods}}
{{/showUsers}}
{{#showGoods}}
<h3>Found goods:</h3>
<div class="goodsSearches">
{{#goods}}
<div class="goodsCard">
<h3>{{title}} # {{id}}</h3>
<p class="description">Category: {{category}}</p>
<a class="goodsLink" href="/goods/{{guid}}">More info...</a>
</div>
{{/goods}}
{{^goods}}
No goods found for this search!
{{/goods}}
</div>
{{/showGoods}}
</section>
</main>
</body>
</html>

View file

@ -0,0 +1,42 @@
<!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>{{user.fulName}}'s page</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Smooch+Sans:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<main id="mainPageMain">
<section class="goodsSection">
<a href="/">&gt;&gt;&gt; Go home</a>
<br>
<a href="javascript:history.back()">&lt;&lt;&lt; Go back</a>
<hr>
<div class="userCard">
<img src="{{user.photo}}" alt="{{user.fullName}} avatar" class="avatar">
<h2>User "{{user.fullName}}"</h2>
</div>
<hr>
<p>User has {{goodsCount}} goods in our store.</p>
<p>Their goods:</p>
<div class="goods">
{{#goods}}
<div class="goodsCard">
<h3>{{title}} # {{id}}</h3>
<h4>Category: {{category}}</h4>
<p class="description">{{description}}</p>
<a class="goodsLink" href="/goods/{{guid}}">More info...</a>
</div>
{{/goods}}
</div>
</section>
</main>
</body>
</html>

BIN
w6/w6.zip Normal file

Binary file not shown.