commit 7d38de64358a91c89292ebdffefb971759138cd2 Author: rawley fowler Date: Mon Aug 29 21:04:10 2022 -0600 now in ocaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..978e789 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +_esy/ +_build/ +node_modules +esy.lock/ +*.db +*.pem +*~ +posts/*.post diff --git a/db/#inserts.ml# b/db/#inserts.ml# new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/db/#inserts.ml# @@ -0,0 +1 @@ + diff --git a/db/database.ml b/db/database.ml new file mode 100644 index 0000000..8cd6523 --- /dev/null +++ b/db/database.ml @@ -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 diff --git a/db/dune b/db/dune new file mode 100644 index 0000000..f25aa84 --- /dev/null +++ b/db/dune @@ -0,0 +1,4 @@ +(library + (name database) + (public_name rawleydotxyz.database) + (libraries caqti caqti-lwt caqti-driver-sqlite3 lwt)) \ No newline at end of file diff --git a/db/inserts.ml b/db/inserts.ml new file mode 100644 index 0000000..719330d --- /dev/null +++ b/db/inserts.ml @@ -0,0 +1,3 @@ +module Inserts = struct + +end diff --git a/dune b/dune new file mode 100644 index 0000000..e107a4a --- /dev/null +++ b/dune @@ -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) \ No newline at end of file diff --git a/dune-project b/dune-project new file mode 100644 index 0000000..c8e724b --- /dev/null +++ b/dune-project @@ -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)) \ No newline at end of file diff --git a/esy.json b/esy.json new file mode 100644 index 0000000..aa73cd7 --- /dev/null +++ b/esy.json @@ -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" + } +} diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..18addc4 --- /dev/null +++ b/html/index.html @@ -0,0 +1,60 @@ + + + + + + + rawley.xyz + + + +
+

+ /home/rawley.xyz +

+ +
+
+

Welcome!

+

+ 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. +

+

+ All of my personal projects are Free Software and can be + found on my git server or, on my github. + I spend most of my free time working on personal projects, + writing blog posts, or listening to bluegrass music. +

+

+ If you need to contact me, please send me an email. +

+
+ + + diff --git a/html/philosophy.html b/html/philosophy.html new file mode 100644 index 0000000..67547d9 --- /dev/null +++ b/html/philosophy.html @@ -0,0 +1,63 @@ + + + + + + + rawley.xyz/philosophy + + + + +
+

+ /home/rawley.xyz +

+ +
+
+

Philosophy

+

+ 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. +

+
+ + + diff --git a/html/resume.html b/html/resume.html new file mode 100644 index 0000000..dc7377a --- /dev/null +++ b/html/resume.html @@ -0,0 +1,127 @@ + + + + + + + rawley.xyz/resume + + + + +
+

+ /home/rawley.xyz +

+ +
+
+

Rawley Fowler ~ Software Developer

+

+ I am an experienced and pragmatic software developer from + Canada with a particular interest in functional programming, + operating systems and programming language theory. +

+

Skills

+
Operating Systems
+

+ Debian Linux + Fedora Linux + OpenBSD + Void Linux + FreeBSD +

+
Programming Languages
+

+ OCaml + Scheme + Clojure + Go + Bash + ELisp + C/C++ + Perl + Ruby + JavaScript + TypeScript + Java +

+
Misc
+

+ Git + CMake + Dune + Esy + OracleSQL + Docker + Docker-Compose + PostgreSQL + SQLite3 + MySQL +

+

Experience

+ DevPro Consulting: Fullstack Developer Consultant (2022 - Present) +

+ Full stack microservice web development with Java, + Spring Boot, Web Sockets, Angular and SCSS. +

+

Projects & Contributions

+ Sluj: + The small and efficient URI slugging library for Clojure. +

+ Sluj is the only 100% Clojure slugging solution for the JVM. It's goal + is to be as simple, and efficient as possible. +

+ This Website: + My website and blog written in OCaml. +

+ My website, rawley.xyz 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. +

+ why-openbsd.rocks + A website for getting random facts about OpenBSD. +

+ Reworked/updated the JavaScript used on why-openbsd.rocks. + Previously the code was using XMLHttpRequests directly, + I refactored it to use the newer, more standard fetch API. +

+
+ + + diff --git a/html/web-ring.html b/html/web-ring.html new file mode 100644 index 0000000..8825172 --- /dev/null +++ b/html/web-ring.html @@ -0,0 +1,79 @@ + + + + + + + rawley.xyz/web-ring + + + + +
+

+ /home/rawley.xyz +

+ +
+
+

Web Ring

+

+ These are sites I like, sites that my friends host, or sites that I think are + useful in some way. +

+
+ + based.cooking + + + rawley.xyz + + + tomfasano.net + + + monero + + + ban porn + + + debian linux + + + dfitw.xyz + + + GNU Emacs + +
+
+ + + diff --git a/insert-posts.sh b/insert-posts.sh new file mode 100755 index 0000000..e0e88b8 --- /dev/null +++ b/insert-posts.sh @@ -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 diff --git a/posts/.gitkeep b/posts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/rawleydotxyz.ml b/rawleydotxyz.ml new file mode 100644 index 0000000..ce4bd09 --- /dev/null +++ b/rawleydotxyz.ml @@ -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 + ]; + ] diff --git a/render/dune b/render/dune new file mode 100644 index 0000000..c48986b --- /dev/null +++ b/render/dune @@ -0,0 +1,4 @@ +(library + (name render) + (public_name rawleydotxyz.render) + (libraries dream lwt database)) \ No newline at end of file diff --git a/render/render.ml b/render/render.ml new file mode 100644 index 0000000..48a1c22 --- /dev/null +++ b/render/render.ml @@ -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| + + + + + rawley.xyz + + + + +
+

+ /home/rawley.xyz +

+ +
+
+ |eos} + + let footer_template = + {eos| +
+ + + + |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| + + |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 "

%s

\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 "

Blog

" 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 "
No blog posts..." in + Dream.html c +end diff --git a/static/1661749187152.png b/static/1661749187152.png new file mode 100644 index 0000000..3f0187a Binary files /dev/null and b/static/1661749187152.png differ diff --git a/static/banporn.gif b/static/banporn.gif new file mode 100644 index 0000000..427b8bc Binary files /dev/null and b/static/banporn.gif differ diff --git a/static/basedcooking.gif b/static/basedcooking.gif new file mode 100644 index 0000000..a9b1396 Binary files /dev/null and b/static/basedcooking.gif differ diff --git a/static/debian.gif b/static/debian.gif new file mode 100644 index 0000000..3a4947b Binary files /dev/null and b/static/debian.gif differ diff --git a/static/dfitw.xyz.png b/static/dfitw.xyz.png new file mode 100644 index 0000000..2a5a905 Binary files /dev/null and b/static/dfitw.xyz.png differ diff --git a/static/emacs.png b/static/emacs.png new file mode 100644 index 0000000..6846a5c Binary files /dev/null and b/static/emacs.png differ diff --git a/static/getmonero.gif b/static/getmonero.gif new file mode 100644 index 0000000..e3e54c9 Binary files /dev/null and b/static/getmonero.gif differ diff --git a/static/index.css b/static/index.css new file mode 100644 index 0000000..f1659d5 --- /dev/null +++ b/static/index.css @@ -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; + } +} diff --git a/static/rawley.xyz.png b/static/rawley.xyz.png new file mode 100644 index 0000000..9d3a68b Binary files /dev/null and b/static/rawley.xyz.png differ diff --git a/static/tomfasano.gif b/static/tomfasano.gif new file mode 100644 index 0000000..ab92936 Binary files /dev/null and b/static/tomfasano.gif differ