Moved to poem + markdown + htmx
This commit is contained in:
parent
79ff1b09f3
commit
3edd78c3da
42 changed files with 129 additions and 7930 deletions
0
polsevev_dev_backend/.gitignore → .gitignore
vendored
0
polsevev_dev_backend/.gitignore → .gitignore
vendored
7
Blog/KubernetesCluster copy.md
Normal file
7
Blog/KubernetesCluster copy.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# I made my own Kubernetes cluster!
|
||||
|
||||
I have for a looong time wanted to have my own kubernetes cluster for my homelab. Just because it really sounds fun and learning a new skill is something i love!
|
||||
|
||||
You wanna know something cool? This very website is actually hosted on this actual cluster!
|
||||
|
||||
Here it is in action!
|
7
Blog/KubernetesCluster.md
Normal file
7
Blog/KubernetesCluster.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# I made my own Kubernetes cluster!
|
||||
|
||||
I have for a looong time wanted to have my own kubernetes cluster for my homelab. Just because it really sounds fun and learning a new skill is something i love!
|
||||
|
||||
You wanna know something cool? This very website is actually hosted on this actual cluster!
|
||||
|
||||
Here it is in action!
|
|
@ -6,7 +6,7 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
poem = "1.2"
|
||||
poem-openapi = { version = "1.2", features = ["swagger-ui"] }
|
||||
poem = "3.0.1"
|
||||
poem-openapi = { version = "5.0.2", features = ["swagger-ui", "static-files"] }
|
||||
pulldown-cmark = "0.11.0"
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
||||
sqlx = { version = "0.6.0", features = ["runtime-tokio-rustls", "postgres"] }
|
|
@ -1,18 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
24
polsevev.dev.frontend/.gitignore
vendored
24
polsevev.dev.frontend/.gitignore
vendored
|
@ -1,24 +0,0 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"tabWidth": 4
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||
|
||||
- Configure the top-level `parserOptions` property like this:
|
||||
|
||||
```js
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: ['./tsconfig.json', './tsconfig.node.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
```
|
||||
|
||||
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
|
||||
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
|
||||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
|
|
@ -1,15 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
5040
polsevev.dev.frontend/package-lock.json
generated
5040
polsevev.dev.frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,31 +0,0 @@
|
|||
{
|
||||
"name": "polsevev.dev.frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router": "^6.15.0",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"styled-components": "^6.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5"
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 4.7 KiB |
|
@ -1,11 +0,0 @@
|
|||
import React from "react"
|
||||
|
||||
|
||||
const About: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<h2>ABOUT</h2>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default About;
|
|
@ -1,12 +0,0 @@
|
|||
import { FC } from "react";
|
||||
|
||||
|
||||
const CVPage:FC = () => {
|
||||
return (
|
||||
<>
|
||||
<h1>CVPage</h1>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CVPage;
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
|
||||
const ErrorPage:React.FC = () => {
|
||||
|
||||
return (<>Not found!</>)
|
||||
}
|
||||
|
||||
export default ErrorPage
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import { IntroBox, IntroParagraph } from "../styles/HomePageStyle";
|
||||
|
||||
const HomePage: React.FC = () => {
|
||||
return (
|
||||
<div style={{minWidth:"100%"}}>
|
||||
<IntroBox>
|
||||
<IntroParagraph>
|
||||
I am Rolf Martin Glomsrud, welcome to my website!
|
||||
Here you can find my most interesting/coolest projects, my CV, and more!
|
||||
|
||||
</IntroParagraph>
|
||||
|
||||
</IntroBox>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomePage;
|
|
@ -1,13 +0,0 @@
|
|||
import { FC } from "react";
|
||||
|
||||
|
||||
const ProjectPage:FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>ProjectPage!</h1>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProjectPage;
|
|
@ -1,25 +0,0 @@
|
|||
import React from "react"
|
||||
import { Outlet } from "react-router";
|
||||
import TopBar from "../components/TopBar";
|
||||
import { ThemeProvider, styled } from "styled-components";
|
||||
import theme from "../styles/Theme";
|
||||
|
||||
|
||||
const OutletBox = styled.div`
|
||||
margin-top: 6em;
|
||||
`
|
||||
|
||||
const Root: React.FC = () => {
|
||||
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<TopBar/>
|
||||
<OutletBox>
|
||||
|
||||
<Outlet/>
|
||||
</OutletBox>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
export default Root;
|
|
@ -1,48 +0,0 @@
|
|||
import { FC, useEffect, useState } from "react";
|
||||
import { ExitLayer, NavItem, SideBarBox } from "../styles/TopBarStyle";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
const NavigationMenu: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const handleClick = (loc: string) => {
|
||||
setIsNavMenuOpen(false);
|
||||
navigate(loc);
|
||||
};
|
||||
const locations: Array<string> = ["/", "/about", "CV", "/projects"];
|
||||
const [isPhone, setIsPhone] = useState(
|
||||
window.matchMedia("(min-width: 720px)").matches
|
||||
);
|
||||
const locationButtons = locations.map((location) => (
|
||||
<NavItem onClick={() => handleClick(location)}>{location}</NavItem>
|
||||
));
|
||||
const [isNavMenuOpen, setIsNavMenuOpen] = useState<boolean>(false);
|
||||
useEffect(() => {
|
||||
const recheckIsPhone = () => {
|
||||
setIsPhone(window.matchMedia("(min-width: 720px)").matches);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", recheckIsPhone);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{isPhone ? (
|
||||
locationButtons
|
||||
) : (
|
||||
<>
|
||||
<button onClick={() => setIsNavMenuOpen(true)}>
|
||||
<span className="material-symbols-outlined">menu</span>
|
||||
</button>
|
||||
<SideBarBox xPosition={isNavMenuOpen ? 0 : 20}>
|
||||
{locationButtons}
|
||||
</SideBarBox>
|
||||
{isNavMenuOpen && (
|
||||
<ExitLayer onClick={() => setIsNavMenuOpen(false)} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavigationMenu;
|
|
@ -1,22 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
import { GithubLogo, TopBarBox, Wrapper } from "../styles/TopBarStyle";
|
||||
import { BigText } from "../styles/TextStyles";
|
||||
import NavigationMenu from "./NavigationMenu";
|
||||
|
||||
const TopBar: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Wrapper>
|
||||
<TopBarBox href={"https://github.com/polsevev/"}>
|
||||
<GithubLogo src={"/github-mark-white.png"} />
|
||||
<BigText>Polsevev</BigText>
|
||||
</TopBarBox>
|
||||
<TopBarBox>
|
||||
<NavigationMenu />
|
||||
</TopBarBox>
|
||||
</Wrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default TopBar;
|
|
@ -1,79 +0,0 @@
|
|||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
#root{
|
||||
width: 100%;
|
||||
}
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings:
|
||||
'FILL' 0,
|
||||
'wght' 400,
|
||||
'GRAD' 0,
|
||||
'opsz' 24
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
import { RouterProvider } from "react-router";
|
||||
import { createBrowserRouter } from "react-router-dom";
|
||||
import Root from "./Pages/Root.tsx";
|
||||
import About from "./Pages/About.tsx";
|
||||
import ErrorPage from "./Pages/ErrorPage.tsx";
|
||||
import HomePage from "./Pages/HomePage.tsx";
|
||||
import CVPage from "./Pages/CVPage.tsx";
|
||||
import ProjectPage from "./Pages/ProjectPage.tsx";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <Root />,
|
||||
children: [
|
||||
|
||||
|
||||
{ path: "/", element: <HomePage /> },
|
||||
{
|
||||
path: "/about",
|
||||
element: <About />,
|
||||
},
|
||||
{path: "/cv", element: <CVPage/>},
|
||||
{path: "/projects", element: <ProjectPage/>},
|
||||
{ path: "*", element: <ErrorPage /> },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
</React.StrictMode>
|
||||
);
|
|
@ -1,14 +0,0 @@
|
|||
import styled from "styled-components";
|
||||
|
||||
export const IntroBox = styled("div")`
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
export const IntroParagraph = styled("p")`
|
||||
width: 50%;
|
||||
`
|
|
@ -1,14 +0,0 @@
|
|||
import styled from "styled-components";
|
||||
|
||||
export const BigText = styled("p")`
|
||||
font-family: Courier, monospace;
|
||||
font-size: 32px;
|
||||
letter-spacing: 0px;
|
||||
word-spacing: 1.2px;
|
||||
color: #0cc7f6;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
font-style: normal;
|
||||
font-variant: small-caps;
|
||||
text-transform: capitalize;
|
||||
`;
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
const theme = {
|
||||
palette: {
|
||||
jet: "#49424D",
|
||||
cerulean: "#087CA7",
|
||||
aero: "#05B2DC",
|
||||
white: "#FFFFFF"
|
||||
}
|
||||
}
|
||||
export default theme;
|
|
@ -1,61 +0,0 @@
|
|||
import styled from "styled-components";
|
||||
|
||||
export const Wrapper = styled("div")`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: calc(100% - 2em);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 5em;
|
||||
margin-left: 1em;
|
||||
`;
|
||||
|
||||
export const TopBarBox = styled("a")`
|
||||
margin-left: "2px";
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const NavItem = styled("button")`
|
||||
font-size: 20px;
|
||||
margin: 2px;
|
||||
background-color: ${(props) => props.theme.palette.cerulean};
|
||||
font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 24;
|
||||
&:hover {
|
||||
border-color: ${(props) => props.theme.palette.jet};
|
||||
}
|
||||
`;
|
||||
|
||||
export const GithubLogo = styled("img")(() => ({
|
||||
width: "50px",
|
||||
height: "50px",
|
||||
padding: "5px",
|
||||
}));
|
||||
|
||||
export const SideBarBox = styled("div")<{ xPosition: number }>`
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: calc(100% - 10em);
|
||||
overflow: overlay;
|
||||
background-color: ${(props) => props.theme.palette.aero};
|
||||
text-align: center;
|
||||
border-radius: 25px;
|
||||
border: 2px solid #ffffff;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
transform: translateX(${({ xPosition }) => `${xPosition}em`});
|
||||
transition: transform 0.2s ease-in-out;
|
||||
`;
|
||||
|
||||
export const ExitLayer = styled("div")`
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
z-index: 90;
|
||||
transform: translateX(-20px);
|
||||
`;
|
1
polsevev.dev.frontend/src/vite-env.d.ts
vendored
1
polsevev.dev.frontend/src/vite-env.d.ts
vendored
|
@ -1 +0,0 @@
|
|||
/// <reference types="vite/client" />
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
2183
polsevev_dev_backend/Cargo.lock
generated
2183
polsevev_dev_backend/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS articles(
|
||||
id SERIAL PRIMARY KEY,
|
||||
markdown TEXT,
|
||||
name TEXT
|
||||
);
|
|
@ -1,69 +0,0 @@
|
|||
mod mdtransform;
|
||||
use mdtransform::MdTransform;
|
||||
use poem::{
|
||||
error::BadRequest, listener::TcpListener, middleware::AddData, web::Data, EndpointExt, Route,
|
||||
};
|
||||
use poem_openapi::{param::Query, payload::PlainText, OpenApi, OpenApiService};
|
||||
use sqlx::{postgres::PgPoolOptions, Error, Pool, Postgres};
|
||||
use tokio::{
|
||||
fs::{self, File},
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
struct Api {
|
||||
pool: Mutex<Pool<Postgres>>,
|
||||
}
|
||||
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/hello", method = "get")]
|
||||
async fn index(&self, name: Query<Option<String>>) -> PlainText<String> {
|
||||
match name.0 {
|
||||
Some(name) => {
|
||||
let pool = &self.pool.lock().await;
|
||||
insertSomething(pool, &name).await;
|
||||
return PlainText(format!("Inserted article with name: {}", name).to_string());
|
||||
}
|
||||
None => PlainText("hello!".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn insertSomething(pool: &Pool<Postgres>, name: &String) {
|
||||
sqlx::query!(
|
||||
"INSERT INTO articles (markdown, name) VALUES ($1, $2);",
|
||||
"ARTICLE HERE",
|
||||
name
|
||||
)
|
||||
.execute(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), std::io::Error> {
|
||||
let pool = PgPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect("postgres://postgres:localdb@localhost:5432/postgres")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let api_service = OpenApiService::new(
|
||||
Api {
|
||||
pool: Mutex::new(pool),
|
||||
},
|
||||
"Hello World",
|
||||
"1.0",
|
||||
)
|
||||
.server("http://localhost:3000/api");
|
||||
let ui = api_service.swagger_ui();
|
||||
let app = Route::new().nest("/api", api_service).nest("/", ui);
|
||||
|
||||
let file = fs::read_to_string("test.md").await.unwrap();
|
||||
println!("{}", MdTransform::transform(file));
|
||||
|
||||
println!("HELLO!");
|
||||
poem::Server::new(TcpListener::bind("127.0.0.1:3000"))
|
||||
.run(app)
|
||||
.await
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
pub struct MdTransform;
|
||||
|
||||
impl MdTransform {
|
||||
pub fn transform(markdown: String) -> String {
|
||||
let mut lines = markdown
|
||||
.split("\n")
|
||||
.map(|x| x.to_string().chars().collect::<Vec<char>>())
|
||||
.collect::<Vec<Vec<char>>>();
|
||||
|
||||
Self::trans(&mut lines);
|
||||
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
fn trans(markdownLines: &mut Vec<Vec<char>>) {
|
||||
let mut notation: Vec<Vec<char>> = Vec::new();
|
||||
let mut mdLinesIter = markdownLines.into_iter();
|
||||
|
||||
while let Some(current) = mdLinesIter.next() {}
|
||||
}
|
||||
|
||||
fn transformLine(line: &mut [char]) -> String {
|
||||
match line {
|
||||
['#', rest @ ..] => {
|
||||
let mut nrOfHashtags = 0;
|
||||
for i in rest {
|
||||
if *i != '#' {
|
||||
break;
|
||||
}
|
||||
nrOfHashtags += 1;
|
||||
}
|
||||
Self::transformLine(&mut rest[nrOfHashtags..])
|
||||
}
|
||||
['-', '-', '-', rest @ ..] => {
|
||||
let validLine = rest.iter().fold(true, |x, y| x && *y == '-');
|
||||
if validLine {
|
||||
"<hr>".to_string()
|
||||
} else {
|
||||
let mut res = "<p>---".to_string();
|
||||
res.push_str(&rest.iter().collect::<String>());
|
||||
res
|
||||
}
|
||||
}
|
||||
['`', '`', '`', languageName @ ..] => {
|
||||
let mut linesInBlock = Vec::new();
|
||||
|
||||
for z in mdLinesIter
|
||||
.by_ref()
|
||||
.take_while(|x| !matches!(x.as_slice(), ['`', '`', '`', ..]))
|
||||
{
|
||||
linesInBlock.push(z);
|
||||
}
|
||||
|
||||
println!("Found codeblock with contents {:?}", linesInBlock)
|
||||
}
|
||||
[regularText @ ..] => {}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
|
||||
# H1
|
||||
## H2
|
||||
### H3
|
||||
#### H4
|
||||
##### H5
|
||||
|
||||
Regular Text
|
||||
|
||||
> BlockQuote
|
||||
|
||||
1. Item 1
|
||||
2. Item 2
|
||||
3. Item 3
|
||||
|
||||
- first item
|
||||
- second item
|
||||
|
||||
|
||||
`code`
|
||||
|
||||
```
|
||||
Code Block
|
||||
Contents is here
|
||||
Mega
|
||||
Pog!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
[Link](https://google.com)
|
||||
|
||||

|
||||
|
||||
|
||||
|
43
src/main.rs
Normal file
43
src/main.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
mod prepare;
|
||||
use std::fs;
|
||||
|
||||
use poem::{endpoint::StaticFilesEndpoint, listener::TcpListener, Route};
|
||||
use poem_openapi::{param::Query, payload::Html, payload::PlainText, OpenApi, OpenApiService};
|
||||
use prepare::{prep, BlogPost};
|
||||
|
||||
struct Api {
|
||||
blogPosts: Vec<BlogPost>,
|
||||
}
|
||||
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/posts", method = "get")]
|
||||
async fn posts(&self) -> Html<String> {
|
||||
Html(self.blogPosts.iter().fold(String::new(), |posts, post| {
|
||||
posts + "<li>" + &post.name + "</li>"
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), std::io::Error> {
|
||||
// Load files
|
||||
|
||||
let blog = prep("Blog".to_string());
|
||||
|
||||
let api_service = OpenApiService::new(Api { blogPosts: blog }, "Hello World", "1.0")
|
||||
.server("http://localhost:3000/api");
|
||||
|
||||
let ui = api_service.swagger_ui();
|
||||
let app = Route::new()
|
||||
.nest(
|
||||
"/",
|
||||
StaticFilesEndpoint::new("static").index_file("index.html"),
|
||||
)
|
||||
.nest("/api", api_service)
|
||||
.nest("/swagger", ui);
|
||||
|
||||
poem::Server::new(TcpListener::bind("127.0.0.1:3000"))
|
||||
.run(app)
|
||||
.await
|
||||
}
|
40
src/prepare.rs
Normal file
40
src/prepare.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use std::fs;
|
||||
|
||||
use pulldown_cmark::{Options, Parser};
|
||||
|
||||
pub fn prep(blogPath: String) -> Vec<BlogPost>{
|
||||
let blogPaths = fs::read_dir(&blogPath);
|
||||
|
||||
let parsed = match blogPaths {
|
||||
Ok(paths) => paths
|
||||
.into_iter()
|
||||
.map(|x| x.unwrap().path().to_str().unwrap().to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
Err(_) => panic!("Could not load directory of blog"),
|
||||
};
|
||||
|
||||
dbg!(&parsed);
|
||||
|
||||
let mut options = Options::empty();
|
||||
|
||||
|
||||
let compiled_html = parsed.into_iter().map(|mdFilePath| {
|
||||
let fileContents = fs::read_to_string(&mdFilePath).unwrap();
|
||||
let name = mdFilePath.strip_prefix(&format!("{}/", &blogPath)).unwrap().to_string();
|
||||
let parser = Parser::new_ext(&fileContents, options);
|
||||
|
||||
let mut html = String::new();
|
||||
pulldown_cmark::html::push_html(&mut html, parser);
|
||||
BlogPost{
|
||||
name,
|
||||
html
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
compiled_html
|
||||
}
|
||||
|
||||
pub struct BlogPost {
|
||||
pub name: String,
|
||||
pub html: String,
|
||||
}
|
17
static/blog.html
Normal file
17
static/blog.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="style.css"/>
|
||||
<script src="htmx.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div hx-confirm="Are you sure?">
|
||||
<button hx-delete="/account">
|
||||
Delete My Account
|
||||
</button>
|
||||
<button hx-put="/account">
|
||||
Update My Account
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
1
static/htmx.min.js
vendored
Normal file
1
static/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11
static/index.html
Normal file
11
static/index.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="style.css"/>
|
||||
<script src="htmx.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<ul hx-get="/api/posts" hx-swap="innerHTML" hx-trigger="load">
|
||||
This is a test
|
||||
</ul>
|
||||
</body>
|
0
static/style.css
Normal file
0
static/style.css
Normal file
Loading…
Reference in a new issue