update to new arch #2

Merged
pleshevskiy merged 3 commits from v2 into main 2022-06-08 23:24:20 +03:00
27 changed files with 94 additions and 3481 deletions
Showing only changes of commit 3fa54d4ac6 - Show all commits

View file

@ -1,12 +0,0 @@
/*
!/tsconfig.json
# node modules
!/package.json
!/package-lock.json
# sources
!/src
!/static

View file

@ -1,28 +0,0 @@
parser: "@typescript-eslint/parser"
env:
es6: true
node: true
parserOptions:
ecmaVersion: 2020
sourceType: "module"
extends:
- prettier
- plugin:prettier/recommended
- "plugin:@typescript-eslint/recommended"
rules:
"@typescript-eslint/no-unused-vars":
- error
- vars: all
args: after-used
argsIgnorePattern: ^_
varsIgnorePattern: ^_
ignoreRestSiblings: true
"@typescript-eslint/no-empty-interface": off
"@typescript-eslint/no-explicit-any": off
"@typescript-eslint/explicit-function-return-type":
- warn
- allowExpressions: false
allowTypedFunctionExpressions: true
allowHigherOrderFunctions: true
"@typescript-eslint/camelcase": off
"@typescript-eslint/no-use-before-define": off

1
.gitignore vendored
View file

@ -9,6 +9,7 @@
!/*file !/*file
!/*.yml !/*.yml
!/*.json !/*.json
!/*.nix
# sources # sources
!/src !/src

View file

@ -1,3 +0,0 @@
{
"editor.tabSize": 2
}

View file

@ -1,24 +1,13 @@
FROM node:16.14.2-alpine3.14 FROM denoland/deno:alpine-1.22.1
EXPOSE 33334
WORKDIR /app WORKDIR /app
RUN apk update \ USER deno
&& apk upgrade \
&& apk add --no-cache bash git openssh
COPY package*.json ./ ADD . .
# Compile the main app so that it doesn't need to be compiled each startup/entry.
RUN deno cache server.ts
RUN npm install \ CMD ["run", "-A", "server.ts"]
&& apk del bash git openssh
COPY tsconfig.json ./
COPY src ./src
RUN npm run build \
&& npm prune --production
COPY static ./static/
EXPOSE 30000
CMD npm run start

6
deno.json Normal file
View file

@ -0,0 +1,6 @@
{
"compilerOptions": {
"lib": ["deno.ns", "dom"]
},
"importMap": "./import_map.json"
}

View file

@ -1,31 +0,0 @@
version: "3.9"
networks:
rp_public:
external: true
services:
site:
image: pleshevski
networks:
- rp_public
deploy:
replicas: 1
endpoint_mode: vip
update_config:
order: start-first
rollback_config:
order: start-first
labels:
- traefik.enable=true
- traefik.docker.network=rp_public
- traefik.constraint-label=rp_public
- traefik.http.routers.pleshevski_http.rule=Host(`pleshevski.ru`)
- traefik.http.routers.pleshevski_http.entrypoints=http
- traefik.http.routers.pleshevski_http.middlewares=https_redirect
- traefik.http.routers.pleshevski_https.rule=Host(`pleshevski.ru`)
- traefik.http.routers.pleshevski_https.entrypoints=https
- traefik.http.routers.pleshevski_https.tls=true
- traefik.http.routers.pleshevski_https.tls.certresolver=le
- traefik.http.services.pleshevski.loadbalancer.server.port=30000

43
flake.lock Normal file
View file

@ -0,0 +1,43 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1654593855,
"narHash": "sha256-c+SyXvj7THre87OyIdZfRVR+HhI/g1ZDrQ3VUtTuHkU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "033bd4fa9a8fbe0c68a88e925d9a884161044b25",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"utils": "utils"
}
},
"utils": {
"locked": {
"lastModified": 1653893745,
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

22
flake.nix Normal file
View file

@ -0,0 +1,22 @@
{
description = "Pleshevski personal site";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
utils.url = "github:numtide/flake-utils";
};
outputs = {self, nixpkgs, utils}:
let out = system:
let pkgs = nixpkgs.legacyPackages."${system}";
in {
devShell = pkgs.mkShell {
buildInputs = with pkgs; [
gnumake
nodePackages.sass
];
};
};
in with utils.lib; eachSystem defaultSystems out;
}

5
import_map.json Normal file
View file

@ -0,0 +1,5 @@
{
"imports": {
"ren/": "https://git.pleshevski.ru/pleshevskiy/ren/raw/branch/main/ren/"
}
}

View file

@ -1,10 +1,15 @@
PAR := $(MAKE) -j 128 PAR := $(MAKE) -j 128
DOCKER_NAME := pleshevski DOCKER_NAME := recipes
DOCKER_TAG := pleshevski DOCKER_TAG := recipes
watch: watch:
$(PAR) hr ts-w ${PAR} deno-w sass-w
deno-w:
deno run -A --watch server.ts
sass-w:
sass -w styles/main.scss public/styles/main.css
docker-restart: docker-stop docker-run docker-restart: docker-stop docker-run
@ -16,20 +21,3 @@ docker-run:
docker-build: docker-build:
docker build -t ${DOCKER_TAG} . docker build -t ${DOCKER_TAG} .
build: ts
start:
npm run start
hr:
deno run -A ~/sandbox/hr/server.ts target static
ts:
npm run build
ts-w:
NODE_ENV=develop npx tsc-watch --onSuccess "make start"
clean:
rm -rf target

2849
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,20 +0,0 @@
{
"scripts": {
"build": "tsc",
"start": "node target/scripts/main.mjs"
},
"devDependencies": {
"@types/node": "^17.0.21",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"eslint": "^8.10.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"prettier": "^2.5.1",
"tsc-watch": "^4.6.0",
"typescript": "^4.6.2"
},
"dependencies": {
"ren": "github:pleshevskiy/ren"
}
}

View file

@ -1,31 +0,0 @@
import { AnyNode, E, Ea, Elem } from "ren";
import { config } from "../config.mjs";
import { div } from "../utils.mjs";
export function Layout(page: AnyNode): Elem {
return Ea("html", { lang: "ru" }, [
E("head", [
Ea("meta", { charset: "utf-8" }),
Ea("meta", {
name: "viewport",
content: "width=device-width, initial-scale=1",
}),
Ea("link", {
rel: "stylesheet",
href: "/static/styles.css",
}),
E("title", "pleshevski"),
]),
E("body", [div({ id: "root" }, page), config.isDev && HotReloadScript()]),
]);
}
function HotReloadScript(): Elem {
return E(
"script",
`const ws = new WebSocket("ws://localhost:30001");
ws.addEventListener("message", (m) => {
if (m.data === "RELOAD") location.reload();
});`
);
}

View file

@ -1,34 +0,0 @@
import { AnyNode, Ea, Elem } from "ren";
import { Context } from "../context.mjs";
import { div } from "../utils.mjs";
export function PageLayout(ctx: Context, ...children: AnyNode[]): Elem {
return Ea("div", { id: "main" }, [
Header(ctx.locPath),
Ea("div", { class: "content" }, children),
// Footer(),
]);
}
export function Header(locPath: string): Elem {
return Ea("header", { class: "header" }, [
div({ class: "content-width" }, HeaderNav(locPath)),
]);
}
export function HeaderNav(locPath: string): Elem {
return Ea("nav", { class: "main-menu" }, [
NavLink(locPath, "/", "Обо мне"),
NavLink(locPath, "/works", "Работы"),
]);
}
export function NavLink(locPath: string, href: string, text: string): Elem {
const attrs = { href };
if (locPath === href) attrs["aria-current"] = "true";
return Ea("a", attrs, text);
}
export function Footer(): Elem {
return Ea("footer", { class: "footer" }, "footer");
}

View file

@ -1,17 +0,0 @@
export const config = createConfig();
export function createConfig(): Config {
return {
isDev: process.env.NODE_ENV === "develop",
server: { port: 30000 },
};
}
export interface Config {
isDev: boolean;
server: ServerConfig;
}
export interface ServerConfig {
port: number;
}

View file

@ -1,3 +0,0 @@
export interface Context {
locPath: string;
}

View file

@ -1,13 +0,0 @@
export function isNil<T>(v: Nilable<T>): v is Nil {
return v == null;
}
export type Nullable<T> = T | null;
export type Nilable<T> = T | Nil;
export type Nil = null | undefined;
export function isBool(v: unknown): v is boolean {
return typeof v === "boolean";
}

View file

@ -1,7 +0,0 @@
export function info(...args: unknown[]): void {
console.log("[INFO]", ...args);
}
export function debug(...args: unknown[]): void {
console.log("[DEBUG]", ...args);
}

View file

@ -1,7 +0,0 @@
import { createServer } from "./server.mjs";
main();
function main(): void {
createServer();
}

View file

@ -1,110 +0,0 @@
import * as http from "http";
import * as fs from "fs/promises";
import * as path from "path";
import { Layout } from "./components/layout.mjs";
import { config } from "./config.mjs";
import { debug, info } from "./log.mjs";
import { StrRenderer } from "ren";
import { AboutPage } from "./views/about.mjs";
import { E404 } from "./views/e404.mjs";
import { WorksPage } from "./views/works.mjs";
import { Context } from "./context.mjs";
export function createServer(): void {
const server = http.createServer(handleHttpReq);
server.listen(config.server.port, () => {
info(
"[server]",
`Server listening at http://localhost:${config.server.port}`
);
});
}
async function handleHttpReq(
httpReq: http.IncomingMessage,
httpRes: http.ServerResponse
): Promise<void> {
try {
const req = tryIntoAppServerRequest(httpReq);
debug("[server]", { req });
if (req.url.startsWith("/static")) {
const relFilePath = path.join(process.cwd(), req.url.slice(1));
const mimeType = mimeTypeByExt.get(path.extname(relFilePath));
const fileContent = await fs
.readFile(relFilePath, { encoding: "utf-8" })
.catch((_e) => null);
if (fileContent && mimeType) {
httpRes.writeHead(200, { "content-type": mimeType }).end(fileContent);
} else {
httpRes.writeHead(404).end("Not found");
}
} else {
const ctx: Context = { locPath: req.url };
const ren = new StrRenderer();
if (/^[/](?:about[/]?)?$/.test(req.url)) {
httpRes
.writeHead(200, { "content-type": "text/html" })
.end(ren.render(Layout(AboutPage(ctx))));
} else if (/^[/]works[/]?$/.test(req.url)) {
httpRes
.writeHead(200, { "content-type": "text/html" })
.end(ren.render(Layout(WorksPage(ctx))));
} else {
httpRes
.writeHead(404, { "content-type": "text/html" })
.end(ren.render(Layout(E404(ctx))));
}
}
} catch (err) {
if (err instanceof InvalidServerRequest) {
httpRes.writeHead(400).end("Bad request");
} else if (err instanceof UnsupportedRestMethod) {
httpRes.writeHead(405).end("Method not allowed");
}
}
}
const mimeTypeByExt = new Map([[".css", "text/css"]]);
export function tryIntoAppServerRequest(
req: http.IncomingMessage
): ServerRequest {
if (!req.method || !req.url) throw new InvalidServerRequest();
return {
method: tryIntoAppRestMethod(req.method),
url: req.url,
};
}
export interface ServerRequest {
method: RestMethod;
url: string;
}
export class InvalidServerRequest extends Error {}
export function tryIntoAppRestMethod(rest: string): RestMethod {
switch (rest.toUpperCase()) {
case "GET":
return RestMethod.Get;
default:
throw new UnsupportedRestMethod();
}
}
export enum RestMethod {
Get = "GET",
}
export class UnsupportedRestMethod extends Error {}
export interface ServerResponse {
statusCode?: number;
headers?: Headers;
body: string;
}

View file

@ -1,9 +0,0 @@
import { E, Ea, Et } from "ren";
export const p = Et.bind(null, "p");
export const h2 = Et.bind(null, "h2");
export const h3 = Et.bind(null, "h3");
export const div = Ea.bind(null, "div");
export const ul = E.bind(null, "ul");
export const li = E.bind(null, "li");

View file

@ -1,50 +0,0 @@
import { PageLayout } from "../components/page_layout.mjs";
import { AnyNode } from "ren";
import { div, h3, li, p, ul } from "../utils.mjs";
import { Context } from "../context.mjs";
export function AboutPage(ctx: Context): AnyNode {
return PageLayout(
ctx,
div({ class: "content-width responsive-typography" }, [
div({}, [
p("Привет!"),
p("Меня зовут Дмитрий Плешевский."),
p(
"Я ведущий разработчик программного обеспечения, архитектор,",
"руководитель команды, а так же ментор."
),
p(
"Open-source проекты моя страсть! Придумываю, экспериментирую,",
"воплощаю, улучшаю проекты в свое свободное время"
),
p(
"Помимо программирования я люблю готовить и проводить время со своей",
"любимой семьей!"
),
]),
div({}, [
h3("Языки программирования"),
p("Предпочитаю: Rust, TS"),
p("Огромный опыт: Rust, TS, JS, Python"),
p("Ограниченный опыт: Haskell, Java, C#"),
]),
div({}, [
h3("Базы данных"),
p("Предпочитаю: Postgres"),
p("Огромный опыт: Postgres, MySQL, Sqlite, mongo"),
]),
div({}, [
h3("Создание приложений"),
ul([
li("Традиционное (SSR + Forms)"),
li("API (REST/GraphQL/WebSocket/EventSource)"),
li("Динамическое (SPA)"),
li("Гибридное (SSR + SPA)"),
li("Консольные"),
li("Кроссплатформенные"),
]),
]),
])
);
}

View file

@ -1,14 +0,0 @@
import { PageLayout } from "../components/page_layout.mjs";
import { AnyNode, Ea } from "ren";
import { div } from "../utils.mjs";
import { Context } from "../context.mjs";
export function E404(ctx: Context): AnyNode {
return PageLayout(
ctx,
div(
{ class: "content-width" },
Ea("h3", { class: "font-h3" }, "Страница не найдена")
)
);
}

View file

@ -1,32 +0,0 @@
import { PageLayout } from "../components/page_layout.mjs";
import { AnyNode, Ea } from "ren";
import { div, h3, li, ul } from "../utils.mjs";
import { Context } from "../context.mjs";
export function WorksPage(ctx: Context): AnyNode {
return PageLayout(
ctx,
div({ class: "content-width responsive-typography" }, [
h3("Одни из моих последних работ"),
ul([
li(Ea("a", { href: "https://github.com/pleshevskiy/ren" }, "ren")),
li(Ea("a", { href: "https://github.com/pleshevskiy/hwt" }, "hwt")),
li(
Ea(
"a",
{ href: "https://github.com/pleshevskiy/sonic-channel" },
"sonic-channel"
)
),
li(Ea("a", { href: "https://github.com/pleshevskiy/migra" }, "migra")),
li(
Ea(
"a",
{ href: "https://github.com/pleshevskiy/itconfig-rs" },
"itconfig-rs"
)
),
]),
])
);
}

View file

@ -1,150 +0,0 @@
:root {
--min-width: 320px;
--max-width: 1024px;
--default-color-white: #ffffff;
--color-blue: #1966df;
--default-font-size: 16px;
--font-family: system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Roboto,Oxygen-Sans, Ubuntu, Cantarell, "Segoe UI", Verdana, sans-serif;
--font-weight-regular: 400;
}
*, *::before, *::after {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html {
background-color: var(--default-color-white);
font-size: var(--default-font-size);
line-height: 1;
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
body {
font-weight: var(--font-weight-regular);
font-family: var(--font-family);
min-width: var(--min-width);
}
html, body {
height: 100%;
width: 100%;
}
ul, ol {
list-style: none;
}
#root {
min-height: 100vh;
align-items: stretch;
}
#root,
#main {
display: flex;
flex-direction: column;
}
#main,
.content {
flex: 1 0;
}
.header {
padding-top: 1.5rem;
padding-bottom: 1.5rem;
}
.main-menu {
display: flex;
flex-direction: row;
}
// anim
.main-menu > a {
transition: all .2s ease-in-out;
}
.main-menu > a {
color: var(--color-blue);
padding: 0.5rem;
border-radius: 6px;
border: 1px solid var(--color-blue);
text-decoration: none;
}
.main-menu > a:hover,
.main-menu > a[aria-current]:not([aria-current=""]) {
color: var(--default-color-white);
background-color: var(--color-blue);
}
.main-menu > a:not(:last-child) {
margin-right: 1rem;
}
.content-width {
width: 100%;
max-width: var(--max-width);
margin-left: auto;
margin-right: auto;
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.responsive-typography h3 {
font-size: 24px;
}
.responsive-typography > div,
.responsive-typography p,
.responsive-typography li {
font-size: 18px;
line-height: 1.5;
}
.responsive-typography ul,
.responsive-typography ol {
width: 100%;
}
.responsive-typography ul li,
.responsive-typography ol li {
position: relative;
width: 100%;
min-height: 1.5rem;
padding-left: 1.5rem;
}
.responsive-typography ul li::before,
.responsive-typography ol li::before {
content: '';
position: absolute;
top: 0;
left: 0;
}
.responsive-typography ul > li::before {
background-color: var(--color-blue);
border-radius: 50%;
width: 0.5rem;
height: 0.5rem;
margin-top: 0.5rem;
margin-left: 0.25rem;
}
.responsive-typography > * + * {
margin-top: 2rem;
}
.responsive-typography > div + div,
.responsive-typography p + p {
margin-top: 1rem;
}
.responsive-typography li + li {
margin-top: 0.5rem;
}

View file

@ -1,21 +0,0 @@
{
"compilerOptions": {
"outDir": "target/scripts",
"target": "esnext",
"module": "esnext",
"lib": ["dom", "esnext"],
"moduleResolution": "node",
"rootDir": "src",
"forceConsistentCasingInFileNames": true,
"suppressImplicitAnyIndexErrors": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true
},
"include": [
"src/**/*.mts"
],
}