now in ocaml

This commit is contained in:
2022-08-29 21:04:10 -06:00
commit 7d38de6435
27 changed files with 724 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
_esy/
_build/
node_modules
esy.lock/
*.db
*.pem
*~
posts/*.post

1
db/#inserts.ml# Normal file
View File

@@ -0,0 +1 @@

95
db/database.ml Normal file
View File

@@ -0,0 +1,95 @@
open Lwt
module BlogPost = struct
type t = {
slug : string;
title : string;
content : string;
date : string;
}
end
module Q = struct
open Caqti_request.Infix
open Caqti_type.Std
let blog_post =
let open BlogPost in
let encode {slug; title; content; date} = Ok (slug, title, content, date) in
let decode (slug, title, content, date) = Ok {slug; title; content; date} in
let b_rep = Caqti_type.(tup4 string string string string) in
custom ~encode ~decode b_rep
let create_blog_post_table =
unit ->. unit @@
{eos|
CREATE TABLE IF NOT EXISTS blog_post (
slug text PRIMARY KEY,
title text NOT NULL,
content text NOT NULL,
date text NOT NULL,
active TINYINT DEFAULT 1)
|eos}
let create_blog_post =
tup4 string string string string ->. unit @@
"INSERT INTO blog_post (slug, title, content, date) VALUES (?, ?, ?, ?)"
let get_all_blog_posts =
unit ->* blog_post @@
"SELECT slug, title, content, date FROM blog_post WHERE active = 1"
let get_all_blog_posts_for_display =
unit ->* tup2 string string @@
"SELECT slug, title FROM blog_post WHERE active = 1"
let get_blog_post_by_slug =
string ->? blog_post @@
"SELECT slug, title, content, date FROM blog_post WHERE slug = ? AND active = 1"
let update_blog_post_content =
tup2 string string ->. unit @@
"UPDATE blog_post SET content = ? WHERE slug = ?"
let update_blog_post_title =
tup2 string string ->. unit @@
"UPDATE blog_post SET title = ? WHERE slug = ?"
let delete_blog_post =
string ->. unit @@
"UPDATE blog_post SET active = 0 WHERE slug = ?"
end
module Db = (val Caqti_lwt.connect (Uri.of_string "sqlite3:database.db") >>= Caqti_lwt.or_fail |> Lwt_main.run)
(* Wrappers for the generic functions defined in Q *)
module Database = struct
let create_blog_post_table () =
Db.exec Q.create_blog_post_table ()
let create_blog_post slug title content date =
Db.exec Q.create_blog_post (slug, title, content, date)
let update_blog_post_content slug content =
Db.exec Q.update_blog_post_content (content, slug)
let update_blog_post_title slug title =
Db.exec Q.update_blog_post_title (title, slug)
let get_blog_post_by_slug slug =
Db.find_opt Q.get_blog_post_by_slug slug
let get_all_blog_posts () =
Db.collect_list Q.get_all_blog_posts ()
let iter_blog_posts f =
Db.iter_s Q.get_all_blog_posts f ()
let (>>=?) monad func =
monad >>= (function | Ok x -> func x | Error err -> Lwt.return (Error err))
let report_error = function
| Ok () -> Lwt.return_unit
| Error err ->
Lwt_io.eprintl (Caqti_error.show err)
end

4
db/dune Normal file
View File

@@ -0,0 +1,4 @@
(library
(name database)
(public_name rawleydotxyz.database)
(libraries caqti caqti-lwt caqti-driver-sqlite3 lwt))

3
db/inserts.ml Normal file
View File

@@ -0,0 +1,3 @@
module Inserts = struct
end

14
dune Normal file
View File

@@ -0,0 +1,14 @@
(executable
(name rawleydotxyz)
(libraries dream caqti render database caqti-lwt caqti-driver-sqlite3 lwt))
(env
(dev
(flags (:standard -w -32))))
(rule
(targets template.ml)
(deps template.eml.ml)
(action (run dream_eml %{deps} --workspace %{workspace_root})))
(data_only_dirs _esy esy.lock lib node_modules)

23
dune-project Normal file
View File

@@ -0,0 +1,23 @@
(lang dune 3.4)
(name rawleydotxyz)
(generate_opam_files true)
(source
(github rawleyfowler/rawleydotxyz))
(authors "Rawley Fowler")
(maintainers "Rawley Fowler")
(license "AGPL3.0")
(package
(name rawleydotxyz)
(synopsis "Rawley Fowler's website + blog")
(description "Rawley Fowler's personal website and blog, www.rawley.xyz")
(allow_empty)
(depends
ocaml
dune
lwt
dream
caqti
caqti-driver-sqlite))

15
esy.json Normal file
View File

@@ -0,0 +1,15 @@
{
"dependencies": {
"@opam/dream": "*",
"@opam/dune": "*",
"@opam/lwt": "*",
"@opam/caqti": "*",
"@opam/lwt_ppx": "*",
"@opam/caqti-driver-sqlite3": "*",
"ocaml": "4.12.x"
},
"scripts": {
"start": "dune exec --root . ./rawleydotxyz.exe"
}
}

60
html/index.html Normal file
View File

@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Rawley.xyz, Rawley Fowler's blog and personal website">
<title>rawley.xyz</title>
<link href="/static/index.css" rel="stylesheet">
</head>
<body>
<header>
<h1>
<a href="/">/home/rawley.xyz</a>
</h1>
<nav>
<li>
<a href="/blog">blog</a>
</li>
<li>
<a href="/resume">resume</a>
</li>
<li>
<a href="/philosophy">philosophy</a>
</li>
<li>
<a href="/web-ring">web ring</a>
</li>
</nav>
</header>
<main>
<h3>Welcome!</h3>
<p>
Hi, I'm Rawley. I'm a software developer from Saskatchewan, Canada.
I am experienced with a wide variety of technologies, including
but not limited to OCaml, Clojure, Scheme, GNU/Linux, OpenBSD, C/C++,
and Java. I am particularily interested in functional programming and
programming language theory.
</p>
<p>
All of my personal projects are <a href="https://www.gnu.org/philosophy/philosophy.html">Free Software</a> and can be
found on <a href="https://git.rawley.xyz">my git server</a> or, on my <a href="https://github.com/rawleyfowler">github</a>.
I spend most of my free time working on personal projects,
writing blog posts, or listening to bluegrass music.
</p>
<p>
If you need to contact me, please send me an email.
</p>
</main>
<footer>
<hr>
<p>
Copyright &copy; Rawley Fowler 2022
</p>
<p>
<b>Disclaimer</b>: All opinions on this site, are that of my own. They do not
reflect the opinions of any of my employers; past, present, or future.
</p>
</footer>
</body>
</html>

63
html/philosophy.html Normal file
View File

@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Rawley.xyz, Rawley Fowler's software fundamentalist philosophy">
<title>rawley.xyz/philosophy</title>
<link href="/static/index.css" rel="stylesheet">
<style>
h4 {
font-size: 1.2em;
}
h5 {
font-size: 1em;
}
i:before, i:after {
content: "`";
}
</style>
</head>
<body>
<header>
<h1>
<a href="/">/home/rawley.xyz</a>
</h1>
<nav>
<li>
<a href="/blog">blog</a>
</li>
<li>
<a href="/resume">resume</a>
</li>
<li>
<a href="/philosophy">philosophy</a>
</li>
<li>
<a href="/web-ring">web ring</a>
</li>
</nav>
</header>
<main>
<h3>Philosophy</h3>
<p>
Post early 2000's software is bloated, rights infringing, centralized and political.
Long gone are the days of individual software freedom, and individual invention.
The world has changed, and the world has fallen victim to the one and only thing that
could possibly destroy freedom. Corporatist consumerism.
</p>
</main>
<footer>
<hr>
<p>
Copyright &copy; Rawley Fowler 2022
</p>
<p>
<b>Disclaimer</b>: All opinions on this site, are that of my own. They do not
reflect the opinions of any of my employers; past, present, or future.
</p>
</footer>
</body>
</html>

127
html/resume.html Normal file
View File

@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Rawley.xyz, Rawley Fowler's blog and personal website">
<title>rawley.xyz/resume</title>
<link href="/static/index.css" rel="stylesheet">
<style>
h4 {
font-size: 1.2em;
}
h5 {
font-size: 1em;
}
i:before, i:after {
content: "`";
}
</style>
</head>
<body>
<header>
<h1>
<a href="/">/home/rawley.xyz</a>
</h1>
<nav>
<li>
<a href="/blog">blog</a>
</li>
<li>
<a href="/resume">resume</a>
</li>
<li>
<a href="/philosophy">philosophy</a>
</li>
<li>
<a href="/web-ring">web ring</a>
</li>
</nav>
</header>
<main>
<h3>Rawley Fowler ~ Software Developer</h3>
<p>
I am an experienced and pragmatic software developer from
Canada with a particular interest in functional programming,
operating systems and programming language theory.
</p>
<h4>Skills</h4>
<h5>Operating Systems</h5>
<p>
<i>Debian Linux</i>
<i>Fedora Linux</i>
<i>OpenBSD</i>
<i>Void Linux</i>
<i>FreeBSD</i>
</p>
<h5>Programming Languages</h5>
<p>
<i>OCaml</i>
<i>Scheme</i>
<i>Clojure</i>
<i>Go</i>
<i>Bash</i>
<i>ELisp</i>
<i>C/C++</i>
<i>Perl</i>
<i>Ruby</i>
<i>JavaScript</i>
<i>TypeScript</i>
<i>Java</i>
</p>
<h5>Misc</h5>
<p>
<i>Git</i>
<i>CMake</i>
<i>Dune</i>
<i>Esy</i>
<i>OracleSQL</i>
<i>Docker</i>
<i>Docker-Compose</i>
<i>PostgreSQL</i>
<i>SQLite3</i>
<i>MySQL</i>
</p>
<h4>Experience</h4>
<b>DevPro Consulting</b>: Fullstack Developer Consultant (2022 - Present)
<p>
Full stack microservice web development with Java,
Spring Boot, Web Sockets, Angular and SCSS.
</p>
<h4>Projects & Contributions</h4>
<b><a href="https://github.com/rawleyfowler/sluj">Sluj</a></b>:
The small and efficient URI slugging library for Clojure.
<p>
Sluj is the only 100% Clojure slugging solution for the JVM. It's goal
is to be as simple, and efficient as possible.
</p>
<b><a href="https://github.com/rawleyfowler/rawleydotxyz">This Website</a></b>:
My website and blog written in OCaml.
<p>
My website, <a href="/">rawley.xyz</a> is written in OCaml Dream,
and interfaces with a SQLite3 database for my blog using Caqti. My website
is designed to be fairly simple (and used to be written in Go). Right now,
it's hosted on a Debian 11 server and served via NGiNX and OCaml.
</p>
<b><a href="https://github.com/noqqe/why-openbsd.rocks">why-openbsd.rocks</a></b>
A website for getting random facts about OpenBSD.
<p>
Reworked/updated the JavaScript used on <a href="https://why-openbsd.rocks">why-openbsd.rocks</a>.
Previously the code was using XMLHttpRequests directly,
I refactored it to use the newer, more standard fetch API.
</p>
</main>
<footer>
<hr>
<p>
Copyright &copy; Rawley Fowler 2022
</p>
<p>
<b>Disclaimer</b>: All opinions on this site, are that of my own. They do not
reflect the opinions of any of my employers; past, present, or future.
</p>
</footer>
</body>
</html>

79
html/web-ring.html Normal file
View File

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Rawley.xyz, Rawley Fowler's web ring">
<title>rawley.xyz/web-ring</title>
<link href="/static/index.css" rel="stylesheet">
<style>
main a {
text-decoration: none;
}
</style>
</head>
<body>
<header>
<h1>
<a href="/">/home/rawley.xyz</a>
</h1>
<nav>
<li>
<a href="/blog">blog</a>
</li>
<li>
<a href="/resume">resume</a>
</li>
<li>
<a href="/philosophy">philosophy</a>
</li>
<li>
<a href="/web-ring">web ring</a>
</li>
</nav>
</header>
<main>
<h3>Web Ring</h3>
<p>
These are sites I like, sites that my friends host, or sites that I think are
useful in some way.
</p>
<div>
<a href="https://based.cooking">
<img src="/static/basedcooking.gif" alt="based.cooking" />
</a>
<a href="/">
<img src="/static/rawley.xyz.png" alt="rawley.xyz" />
</a>
<a href="https://tomfasano.net">
<img src="/static/tomfasano.gif" alt="tomfasano.net" />
</a>
<a href="https://www.getmonero.org">
<img src="/static/getmonero.gif" alt="monero" />
</a>
<a>
<img src="/static/banporn.gif" alt="ban porn" />
</a>
<a href="https://www.debian.org">
<img src="/static/debian.gif" alt="debian linux" />
</a>
<a href="https://dfitw.xyz">
<img src="/static/dfitw.xyz.png" alt="dfitw.xyz" />
</a>
<a href="https://www.gnu.org/software/emacs">
<img src="/static/emacs.png" alt="GNU Emacs" />
</a>
</div>
</main>
<footer>
<hr>
<p>
Copyright &copy; Rawley Fowler 2022
</p>
<p>
<b>Disclaimer</b>: All opinions on this site, are that of my own. They do not
reflect the opinions of any of my employers; past, present, or future.
</p>
</footer>
</body>
</html>

66
insert-posts.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/bin/bash
function usage() {
echo "Usage: insert-posts"
echo "Tool for inserting posts into a SQLite3 database."
echo 'Posts should be located in $BLOG_DIR/posts, and the database should be in $BLOG_DIR'
exit 1
}
[ ! -x "$(command -v sqlite3)" ] && echo "You need SQLite3 to run this" && exit 1
[ -z "$BLOG_DIR" ] && echo '$BLOG_DIR is not set' && usage
# params -> slug, title, content, date
function update_post() {
local date=${1:?Date is required}
local slug=${2:?Slug is required}
local title=${3:?Title is required}
local content=${4:?Content is required}
sqlite3 "$BLOG_DIR/database.db" "UPDATE blog_post SET title = '$title', content = '$content' WHERE slug = '$slug'" 2&> /dev/null
local status=$?
echo $status
[ $status -eq 0 ] && echo "Updated already existing post: $slug."
[ $status -eq 1 ] && echo "Failed to update, or insert post: $slug."
}
# params -> slug, title, content, date
function insert_post() {
[ $# -eq 0 ] && exit 1;
local date=${1:?Date is required}
local slug=${2:?Slug is required}
local title=${3:?Title is required}
local content=${4:?Content is required}
sqlite3 "$BLOG_DIR/database.db" "INSERT INTO blog_post (slug, title, content, date) VALUES ('$slug', '$title', '$content', '$date');" 2&> /dev/null
local status=$?
echo $status
[ $status -eq 0 ] && echo "Inserted $slug successfully." && return
[ $status -eq 19 ] && update_post "$date" "$slug" "$title" "$content"
}
# # # # # # # # # # # # # # # # # # # # # # # #
# Blog post files are structured as follows: #
# FIRST LINE -> date in desired format #
# SECOND LINE -> URI friendly slug #
# THIRD LINE -> Title of post #
# REMAINING LINES -> Content of post #
# # # # # # # # # # # # # # # # # # # # # # # #
for file in $(ls "$BLOG_DIR/posts"); do
echo "Trying to insert: $file"
target_file="$BLOG_DIR/posts/$file"
date=$(sed -n '1p' $target_file)
slug=$(sed -n '2p' $target_file)
title=$(sed -n '3p' $target_file)
content=$(sed -n '4,$p' "$target_file")
insert_post "$date" "$slug" "$title" "$content"
done

0
posts/.gitkeep Normal file
View File

25
rawleydotxyz.ml Normal file
View File

@@ -0,0 +1,25 @@
open Lwt.Infix
open Database
open Render
exception DatabaseCreationFailure
let _ =
Database.create_blog_post_table () >>= function
| Ok () -> Lwt_io.print "Database was initialized successfully\n"
| Error _ -> raise DatabaseCreationFailure
let () =
Dream.run
@@ Dream.logger
@@ Dream.router [
Dream.get "/static/**" @@ Dream.static "static";
Dream.get "/" @@ Dream.from_filesystem "html" "index.html";
Dream.get "/resume" @@ Dream.from_filesystem "html" "resume.html";
Dream.get "/philosophy" @@ Dream.from_filesystem "html" "philosophy.html";
Dream.get "/web-ring" @@ Dream.from_filesystem "html" "web-ring.html";
Dream.get "/blog" Render.render_blog_index;
Dream.scope "/blog" [Dream.origin_referrer_check] [
Dream.get "/:post" Render.render_blog_post
];
]

4
render/dune Normal file
View File

@@ -0,0 +1,4 @@
(library
(name render)
(public_name rawleydotxyz.render)
(libraries dream lwt database))

108
render/render.ml Normal file
View File

@@ -0,0 +1,108 @@
open Database
open Lwt
module Render = struct
let not_found_template = "404 Not found"
let error_template = "500 Server Error"
let header_template =
{eos|
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Rawley.xyz, Rawley Fowler's blog and personal website">
<title>rawley.xyz</title>
<link href="/static/index.css" rel="stylesheet">
</head>
<body>
<body>
<header>
<h1>
<a href="/">/home/rawley.xyz</a>
</h1>
<nav>
<li>
<a href="/blog">blog</a>
</li>
<li>
<a href="/resume">resume</a>
</li>
<li>
<a href="/philosophy">philosophy</a>
</li>
<li>
<a href="/web-ring">web ring</a>
</li>
</nav>
</header>
<main>
|eos}
let footer_template =
{eos|
</main>
<footer>
<hr>
<p>
Copyright &copy; Rawley Fowler 2022
</p>
<p>
<b>Disclaimer</b>: All opinions on this site, are that of my own. They do not
reflect the opinions of any of my employers; past, present, or future.
</p>
</footer>
</body>
</html>
|eos}
let render_page content =
Printf.sprintf
{eos|
%s
%s
%s
|eos}
header_template
content
footer_template
let generate_link (p : BlogPost.t) =
Printf.sprintf
{eos|
<div class="link-wrapper">
<a href="/blog/%s">%s</a>
<i>%s</i>
</div>
|eos}
p.slug p.title p.date
let handle_error e =
print_endline (Caqti_error.show e); error_template |> Dream.html
let render_blog_post request =
let slug = Dream.param request "post" in
let post_t = Database.get_blog_post_by_slug slug in
post_t >>= fun post ->
match post with
| Error e -> handle_error e
| Ok p_opt ->
match p_opt with
| None -> not_found_template |> Dream.html
| Some p ->
Printf.sprintf "<h2>%s</h2>\r\n%s" p.title p.content
|> render_page
|> Dream.html
let render_blog_index _ =
let buff = Buffer.create 512 in
let () = Buffer.add_string buff "<h3>Blog</h3>" in
let posts_t = Database.get_all_blog_posts () in
posts_t >>= function
| Error e -> handle_error e
| Ok posts ->
List.iter (fun p -> Buffer.add_string buff (generate_link p)) posts;
let c = if List.length posts <> 0 then
render_page @@ Buffer.contents buff
else render_page "<br>No blog posts..." in
Dream.html c
end

BIN
static/1661749187152.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

BIN
static/banporn.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
static/basedcooking.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

BIN
static/debian.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
static/dfitw.xyz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
static/emacs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
static/getmonero.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

29
static/index.css Normal file
View File

@@ -0,0 +1,29 @@
body {
font-size: calc(14px + 0.11vw);
width: clamp(373px, 45vw, 50vw);
color: black;
margin: auto;
padding: 2px;
}
nav {
font-size: calc(16px + 0.33vh);
display: flex;
flex-direction: row;
list-style-type: none;
}
nav > * {
margin-right: 33px;
}
@media (prefers-color-scheme: dark) {
html {
background-color: #222222;
}
body * {
color: lightgrey;
}
}

BIN
static/rawley.xyz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
static/tomfasano.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB