summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpolwex <polwex@sortug.com>2025-06-27 16:34:09 +0700
committerpolwex <polwex@sortug.com>2025-06-27 16:34:09 +0700
commit645e815ebe11dbb86781c3eb645d3d67cd62cf7c (patch)
tree347b03391a5245de8a43198c9646719c2e892373
parent8be9a806a93b02eada372f3993c34bc6b2f26fea (diff)
nice nice. lsp still doesnt work tho
-rw-r--r--CLAUDE.md6
-rw-r--r--bin/server.ml2
-rw-r--r--bs5.opam6
-rw-r--r--dune-project14
-rw-r--r--flake.nix3
-rw-r--r--lib/dune1
-rw-r--r--lib/handler.ml232
-rw-r--r--lib/html.ml13
-rw-r--r--lib/pages/BlogIndex.mlx76
-rw-r--r--lib/pages/BlogIndex.re11
-rw-r--r--lib/pages/components/Navbar.mlx34
-rw-r--r--lib/pages/components/SiteTitle.mlx10
-rw-r--r--lib/pages/dune4
-rw-r--r--lib/pages/lmao.re40
-rw-r--r--lib/post_handlers.ml185
-rw-r--r--lib/router.ml4
-rw-r--r--lib/shared/dune8
-rw-r--r--lib/shared/query.ml (renamed from lib/query.ml)103
18 files changed, 497 insertions, 255 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index 8c6cf38..efbbe42 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,8 +1,12 @@
# OCaml SSR React APP on Eio
-This app is a WIP to implement a blog as a React webapp (using reason-react) using Ocaml, Piaf for HTTP handling, Caqti to handle database queries, using Eio across the app for async.
+This app is a WIP to implement a blog as a React webapp using Ocaml, mlx for JSX, Piaf for HTTP handling, Caqti to handle database queries, using Eio across the app for async.
+## Build the app
+To compile the app and see if the code is correct do:
+`dune clean && dune build`
+
## Things to take into account
diff --git a/bin/server.ml b/bin/server.ml
index d640095..f5a8830 100644
--- a/bin/server.ml
+++ b/bin/server.ml
@@ -45,7 +45,7 @@ let () =
~stdenv
~post_connect:(fun conn ->
(* Initialize each connection with SQLite performance pragmas *)
- match Lib.Query.init_connection conn with
+ match Shared.Query.init_connection conn with
| Ok () -> Ok ()
| Error err -> Error err)
uri
diff --git a/bs5.opam b/bs5.opam
index 3b957d2..cd6ac58 100644
--- a/bs5.opam
+++ b/bs5.opam
@@ -11,7 +11,10 @@ doc: "https://url/to/documentation"
bug-reports: "https://github.com/username/reponame/issues"
depends: [
"ocaml"
- "dune" {>= "3.13"}
+ "dune" {>= "3.19"}
+ "ocaml-lsp-server"
+ "ocamlmerlin-mlx"
+ "ocamlformat-mlx"
"odoc" {with-doc}
]
build: [
@@ -29,3 +32,4 @@ build: [
]
]
dev-repo: "git+https://github.com/username/reponame.git"
+x-maintenance-intent: ["(latest)"]
diff --git a/dune-project b/dune-project
index b78e6f4..0e7879b 100644
--- a/dune-project
+++ b/dune-project
@@ -1,4 +1,4 @@
-(lang dune 3.13)
+(lang dune 3.19)
(name bs5)
@@ -19,8 +19,18 @@
(name bs5)
(synopsis "A short synopsis")
(description "A longer description")
- (depends ocaml dune)
+ (depends ocaml dune ocaml-lsp-server ocamlmerlin-mlx ocamlformat-mlx)
(tags
(topics "to describe" your project)))
+(dialect
+ (name mlx)
+ (implementation
+ (extension mlx)
+ (merlin_reader mlx)
+ (format
+ (run ocamlformat-mlx %{input-file}))
+ (preprocess
+ (run mlx-pp %{input-file}))))
+
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
diff --git a/flake.nix b/flake.nix
index 94c1c5a..b750030 100644
--- a/flake.nix
+++ b/flake.nix
@@ -107,6 +107,9 @@
findlib
ocaml-lsp
ocamlformat
+ mlx
+ ocamlmerlin-mlx
+ ocamlformat-mlx
melange-json-native
ppx_rapper
ppx_rapper_eio
diff --git a/lib/dune b/lib/dune
index a50de52..33e494f 100644
--- a/lib/dune
+++ b/lib/dune
@@ -2,6 +2,7 @@
(name lib)
(libraries
; local
+ shared
pages
;
piaf
diff --git a/lib/handler.ml b/lib/handler.ml
index 3408184..16ada90 100644
--- a/lib/handler.ml
+++ b/lib/handler.ml
@@ -1,3 +1,4 @@
+open Shared
open Piaf
type pool = ((module Rapper_helper.CONNECTION), Caqti_error.t) Caqti_eio.Pool.t
@@ -8,26 +9,27 @@ let get_posts (db_pool : pool) (_request : Request.t) =
(* Use a connection from the pool. Caqti_eio.Pool.use handles acquiring and releasing the connection. *)
Caqti_eio.Pool.use
(fun conn ->
- (* Call the get_poasts function from the Query module to fetch posts from the database. *)
- let posts_or_error = Query.get_poasts () conn in
- (* Pattern match on the result of the database query. *)
- match posts_or_error with
- (* If the query is successful, the result is a list of posts. *)
- | Ok posts ->
- (* Map the list of post tuples to a Yojson list. *)
- let json =
- `List
- ((* For each post tuple, create a JSON object. *)
- List.map
- (fun (post : Query.post_summary) -> `Assoc [ "title", `String post.title ])
- posts)
- in
- (* Return a 200 OK response with the JSON body. *)
- Ok (Response.of_string ~body:(Yojson.Safe.to_string json) `OK)
- (* If the query fails, log the error and return a 500 Internal Server Error response. *)
- | Error err ->
- Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
- Ok (Response.create `Internal_server_error))
+ (* Call the get_poasts function from the Query module to fetch posts from the database. *)
+ let posts_or_error = Query.get_poasts () conn in
+ (* Pattern match on the result of the database query. *)
+ match posts_or_error with
+ (* If the query is successful, the result is a list of posts. *)
+ | Ok posts ->
+ (* Map the list of post tuples to a Yojson list. *)
+ let json =
+ `List
+ ((* For each post tuple, create a JSON object. *)
+ List.map
+ (fun (post : Query.post_summary) ->
+ `Assoc [ "title", `String post.title ])
+ posts)
+ in
+ (* Return a 200 OK response with the JSON body. *)
+ Ok (Response.of_string ~body:(Yojson.Safe.to_string json) `OK)
+ (* If the query fails, log the error and return a 500 Internal Server Error response. *)
+ | Error err ->
+ Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
+ Ok (Response.create `Internal_server_error))
db_pool
;;
@@ -35,24 +37,24 @@ let get_posts (db_pool : pool) (_request : Request.t) =
let get_post post_id (db_pool : pool) (_request : Request.t) =
Caqti_eio.Pool.use
(fun conn ->
- let post_or_error = Query.get_poast post_id conn in
- match post_or_error with
- | Ok (Some post) ->
- let json =
- `Assoc
- [ "id", `Int post.id
- ; "title", `String post.title
- ; "content", `String post.content
- ; "date", `String post.date
- ; "tags", `String post.tags
- ; "url", `String post.url
- ]
- in
- Ok (Response.of_string ~body:(Yojson.Safe.to_string json) `OK)
- | Ok None -> Ok (Response.create `Not_found)
- | Error err ->
- Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
- Ok (Response.create `Internal_server_error))
+ let post_or_error = Query.get_poast post_id conn in
+ match post_or_error with
+ | Ok (Some post) ->
+ let json =
+ `Assoc
+ [ "id", `Int post.id
+ ; "title", `String post.title
+ ; "content", `String post.content
+ ; "date", `String post.date
+ ; "tags", `String post.tags
+ ; "url", `String post.url
+ ]
+ in
+ Ok (Response.of_string ~body:(Yojson.Safe.to_string json) `OK)
+ | Ok None -> Ok (Response.create `Not_found)
+ | Error err ->
+ Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
+ Ok (Response.create `Internal_server_error))
db_pool
;;
@@ -60,23 +62,23 @@ let get_post post_id (db_pool : pool) (_request : Request.t) =
let get_comment comment_id (db_pool : pool) (_request : Request.t) =
Caqti_eio.Pool.use
(fun conn ->
- let comment_or_error = Query.Query.comment ~id:comment_id conn in
- match comment_or_error with
- | Ok (Some comment) ->
- let json =
- `Assoc
- [ "id", `Int comment.id
- ; "content", `String comment.content
- ; "date", `String comment.date
- ; "tags", `String comment.tags
- ; "url", `String comment.url
- ]
- in
- Ok (Response.of_string ~body:(Yojson.Safe.to_string json) `OK)
- | Ok None -> Ok (Response.create `Not_found)
- | Error err ->
- Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
- Ok (Response.create `Internal_server_error))
+ let comment_or_error = Query.Query.comment ~id:comment_id conn in
+ match comment_or_error with
+ | Ok (Some comment) ->
+ let json =
+ `Assoc
+ [ "id", `Int comment.id
+ ; "content", `String comment.content
+ ; "date", `String comment.date
+ ; "tags", `String comment.tags
+ ; "url", `String comment.url
+ ]
+ in
+ Ok (Response.of_string ~body:(Yojson.Safe.to_string json) `OK)
+ | Ok None -> Ok (Response.create `Not_found)
+ | Error err ->
+ Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
+ Ok (Response.create `Internal_server_error))
db_pool
;;
@@ -84,26 +86,26 @@ let get_comment comment_id (db_pool : pool) (_request : Request.t) =
let get_user_comments username (db_pool : pool) (_request : Request.t) =
Caqti_eio.Pool.use
(fun conn ->
- let comments_or_error = Query.Query.user_comments ~username conn in
- match comments_or_error with
- | Ok comments ->
- let json =
- `List
- (List.map
- (fun (comment : Query.comment) ->
- `Assoc
- [ "id", `Int comment.id
- ; "content", `String comment.content
- ; "date", `String comment.date
- ; "tags", `String comment.tags
- ; "url", `String comment.url
- ])
- comments)
- in
- Ok (Response.of_string ~body:(Yojson.Safe.to_string json) `OK)
- | Error err ->
- Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
- Ok (Response.create `Internal_server_error))
+ let comments_or_error = Query.Query.user_comments ~username conn in
+ match comments_or_error with
+ | Ok comments ->
+ let json =
+ `List
+ (List.map
+ (fun (comment : Query.comment) ->
+ `Assoc
+ [ "id", `Int comment.id
+ ; "content", `String comment.content
+ ; "date", `String comment.date
+ ; "tags", `String comment.tags
+ ; "url", `String comment.url
+ ])
+ comments)
+ in
+ Ok (Response.of_string ~body:(Yojson.Safe.to_string json) `OK)
+ | Error err ->
+ Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
+ Ok (Response.create `Internal_server_error))
db_pool
;;
@@ -111,26 +113,26 @@ let get_user_comments username (db_pool : pool) (_request : Request.t) =
let get_post_comments post_id (db_pool : pool) (_request : Request.t) =
Caqti_eio.Pool.use
(fun conn ->
- let comments_or_error = Query.Query.post_comments ~post_id conn in
- match comments_or_error with
- | Ok comments ->
- let json =
- `List
- (List.map
- (fun (comment : Query.comment) ->
- `Assoc
- [ "id", `Int comment.id
- ; "content", `String comment.content
- ; "date", `String comment.date
- ; "tags", `String comment.tags
- ; "url", `String comment.url
- ])
- comments)
- in
- Ok (Response.of_string ~body:(Yojson.Safe.to_string json) `OK)
- | Error err ->
- Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
- Ok (Response.create `Internal_server_error))
+ let comments_or_error = Query.Query.post_comments ~post_id conn in
+ match comments_or_error with
+ | Ok comments ->
+ let json =
+ `List
+ (List.map
+ (fun (comment : Query.comment) ->
+ `Assoc
+ [ "id", `Int comment.id
+ ; "content", `String comment.content
+ ; "date", `String comment.date
+ ; "tags", `String comment.tags
+ ; "url", `String comment.url
+ ])
+ comments)
+ in
+ Ok (Response.of_string ~body:(Yojson.Safe.to_string json) `OK)
+ | Error err ->
+ Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
+ Ok (Response.create `Internal_server_error))
db_pool
;;
@@ -138,25 +140,25 @@ let get_post_comments post_id (db_pool : pool) (_request : Request.t) =
let get_comment_children parent_id (db_pool : pool) (_request : Request.t) =
Caqti_eio.Pool.use
(fun conn ->
- let comments_or_error = Query.Query.comment_children ~post_id:parent_id conn in
- match comments_or_error with
- | Ok comments ->
- let json =
- `List
- (List.map
- (fun (comment : Query.comment) ->
- `Assoc
- [ "id", `Int comment.id
- ; "content", `String comment.content
- ; "date", `String comment.date
- ; "tags", `String comment.tags
- ; "url", `String comment.url
- ])
- comments)
- in
- Ok (Response.of_string ~body:(Yojson.Safe.to_string json) `OK)
- | Error err ->
- Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
- Ok (Response.create `Internal_server_error))
+ let comments_or_error = Query.Query.comment_children ~post_id:parent_id conn in
+ match comments_or_error with
+ | Ok comments ->
+ let json =
+ `List
+ (List.map
+ (fun (comment : Query.comment) ->
+ `Assoc
+ [ "id", `Int comment.id
+ ; "content", `String comment.content
+ ; "date", `String comment.date
+ ; "tags", `String comment.tags
+ ; "url", `String comment.url
+ ])
+ comments)
+ in
+ Ok (Response.of_string ~body:(Yojson.Safe.to_string json) `OK)
+ | Error err ->
+ Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
+ Ok (Response.create `Internal_server_error))
db_pool
;;
diff --git a/lib/html.ml b/lib/html.ml
index db5ad51..35c7b42 100644
--- a/lib/html.ml
+++ b/lib/html.ml
@@ -1,6 +1,7 @@
open Piaf
+open Shared
-type pool = ((module Rapper_helper.CONNECTION), Caqti_error.t) Caqti_eio.Pool.t
+type pool = (Query.conn, Caqti_error.t) Caqti_eio.Pool.t
(* This is the main handler function for the GET /posts endpoint. *)
(* It takes a database connection pool and a Piaf request as input. *)
@@ -33,7 +34,9 @@ let get_root (db_pool : pool) (request : Request.t) =
db_pool
;;
-let render page (db_pool : pool) (_request : Request.t) =
+type paget = ?key:string -> Query.conn -> unit -> React.element
+
+let render_conn (page : paget) (db_pool : pool) (_request : Request.t) =
(* Caqti_eio.Pool.use *)
(* (fun conn -> *)
(* let posts_or = Query.get_poasts () conn in *)
@@ -44,6 +47,10 @@ let render page (db_pool : pool) (_request : Request.t) =
(* | Ok _posts -> Ok (page |> ReactDOM.renderToString |> Http.send_raw_html)) *)
(* db_pool *)
Caqti_eio.Pool.use
- (fun conn -> Ok (conn |> page |> ReactDOM.renderToString |> Http.send_raw_html))
+ (fun conn -> Ok (page conn () |> ReactDOM.renderToString |> Http.send_raw_html))
db_pool
;;
+
+let render page (_db_pool : pool) (_request : Request.t) =
+ Ok (page |> ReactDOM.renderToString |> Http.send_raw_html)
+;;
diff --git a/lib/pages/BlogIndex.mlx b/lib/pages/BlogIndex.mlx
new file mode 100644
index 0000000..3e036aa
--- /dev/null
+++ b/lib/pages/BlogIndex.mlx
@@ -0,0 +1,76 @@
+open Shared
+
+let header ~title () = <header><h1>title</h1></header>
+
+let page =
+ <html>
+ <body>
+ <header title="Hello, world!" /> <div>(React.string "Some content goes here")</div>
+ </body>
+ </html>
+;;
+
+module Layout = struct
+ let[@react.component] make ~children =
+ <html>
+ <head>
+ <title>(React.string "Bloody Shovel 5")</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta charSet="UTF-8" />
+ </head>
+ <body>children</body>
+ </html>
+ ;;
+end
+
+module PostPreviews = struct
+ let[@react.component] make ~(conn : Query.conn) =
+ let posts =
+ match Query.get_poasts () conn with
+ | Error _err -> []
+ | Ok posts -> posts
+ in
+ let tests = "This is 'a 'weird <thingy>" in
+ <div>
+ (match posts with
+ | [] -> React.string "No posts"
+ | posts ->
+ let ps =
+ posts
+ |> List.map (fun (p : Query.post_summary) ->
+ Logs.info (fun d -> d "poast %s\n" p.title);
+ <h1>(React.string p.title)</h1>)
+ in
+ let pp = React.string tests :: ps in
+ pp |> Array.of_list |> React.array)
+ </div>
+ ;;
+end
+
+let[@react.component] make (conn : Query.conn) =
+ <Layout>
+ <div className="min-h-screen bg-gray-50">
+ <Navbar />
+ <main className="container mx-auto px-4 py-8">
+ <SiteTitle />
+ <div className="max-w-4xl mx-auto space-y-12"><PostPreviews conn /></div>
+ </main>
+ </div>
+ </Layout>
+;;
+
+(* let[@react.component] make (conn : Query.conn) = *)
+(* let posts = *)
+(* match Query.get_poasts () conn with *)
+(* | Error _err -> [] *)
+(* | Ok posts -> posts *)
+(* in *)
+(* <div> *)
+(* (match posts with *)
+(* | [] -> React.string "No posts" *)
+(* | hd :: _tl -> React.string hd.title) *)
+(* </div> *)
+(* ;; *)
+
+(* # formatter = {command = "ocamlformat-mlx", args = ["-", "--impl"]} *)
+(* List.map (fun _p -> <p>(React.string "wtf")</p>) posts |> React.list *)
diff --git a/lib/pages/BlogIndex.re b/lib/pages/BlogIndex.re
deleted file mode 100644
index 50beaed..0000000
--- a/lib/pages/BlogIndex.re
+++ /dev/null
@@ -1,11 +0,0 @@
-[@react.component]
-let make = () => {
- <html>
- <head>
- <meta charSet="utf-8" />
- <title> {React.string("Bloody Shovel 5")} </title>
- </head>
- // <link rel="stylesheet" href="/output.css" />
- <body> <h1> {React.string("Oh hai")} </h1> </body>
- </html>;
-};
diff --git a/lib/pages/components/Navbar.mlx b/lib/pages/components/Navbar.mlx
new file mode 100644
index 0000000..68003fe
--- /dev/null
+++ b/lib/pages/components/Navbar.mlx
@@ -0,0 +1,34 @@
+module Link = struct
+ let[@react.component] make ~children ~href ?(className = "") =
+ let base_class = "hover:text-gray-300 transition-colors" in
+ let clas = Printf.sprintf "%s %s" base_class className in
+ <a className=clas href>children</a>
+ ;;
+end
+
+let rs = React.string
+
+let[@react.component] make () =
+ <header className="bg-black text-white">
+ <div className="flex items-center space-x-6 justify-between py-2 px-4">
+ <Link href="/">
+ <img className="w-[60px]" src="https://s3.spandrell.ch/assets/icons/tianming.svg"
+ />
+ </Link>
+ <nav className="flex items-center space-x-6 text-sm font-medium">
+ <Link href="/blog">(rs "BLOG")</Link>
+ <Link href="/chat">(rs "CHAT")</Link>
+ <Link href="/board">(rs "BOARD")</Link>
+ <Link href="#">(rs "FEED")</Link>
+ <Link href="/tv">(rs "TV")</Link>
+ <Link href="#">(rs "WIKI")</Link>
+ <Link href="#">(rs "BOOKS")</Link>
+ <Link href="#">(rs "ABOUT")</Link>
+ </nav>
+ <div className="flex items-center space-x-6 text-sm font-medium">
+ <Link href="/search"><span>(rs "SEARCH")</span></Link>
+ <Link href="/login"><span>(rs "LOGIN")</span></Link>
+ </div>
+ </div>
+ </header>
+;;
diff --git a/lib/pages/components/SiteTitle.mlx b/lib/pages/components/SiteTitle.mlx
new file mode 100644
index 0000000..cfee4f3
--- /dev/null
+++ b/lib/pages/components/SiteTitle.mlx
@@ -0,0 +1,10 @@
+let[@react.component] make () =
+ <div className="text-center mb-8">
+ <a href="/">
+ <h1 className="text-5xl text-gray-900 mb-2 hover:text-gray-700 transition-colors">
+ (React.string "BLOODY SHOVEL 5")
+ </h1>
+ </a>
+ <p className="text-gray-600 text-2xl italic">(React.string "Nemo nos Salvabit")</p>
+ </div>
+;;
diff --git a/lib/pages/dune b/lib/pages/dune
index 8cc2720..8ec2184 100644
--- a/lib/pages/dune
+++ b/lib/pages/dune
@@ -3,6 +3,10 @@
(library
(name pages)
(libraries
+ ; local
+ shared
+ ;
+ logs
lwt.unix
server-reason-react.belt
server-reason-react.js
diff --git a/lib/pages/lmao.re b/lib/pages/lmao.re
new file mode 100644
index 0000000..24c630e
--- /dev/null
+++ b/lib/pages/lmao.re
@@ -0,0 +1,40 @@
+[@react.component]
+let make = () => {
+ <html>
+
+ <head>
+ <meta charSet="utf-8" />
+ <title> {React.string("Bloody Shovel 5")} </title>
+ </head>
+ <body> <h1> {React.string("Oh hai")} </h1> </body>
+ </html>;
+ // let _lol = conn;
+ // let make = (~conn) => {
+};
+// [@react.component]
+// let make = (~conn: Query.conn) => {
+// let posts =
+// switch (Query.get_poasts(conn)) {
+// | Ok(posts) => posts
+// | Error(_) => []
+// };
+
+// <html>
+// <head>
+// <meta charSet="utf-8" />
+// <title> {React.string("Bloody Shovel 5")} </title>
+// </head>
+// <body>
+// <h1> {React.string("Oh hai")} </h1>
+// <ul>
+// {posts
+// |> List.map(post =>
+// <li key={string_of_int(post.Query.id)}>
+// {React.string(post.title)}
+// </li>
+// )
+// |> React.array}
+// </ul>
+// </body>
+// </html>;
+// };
diff --git a/lib/post_handlers.ml b/lib/post_handlers.ml
index 2035792..dc0f43b 100644
--- a/lib/post_handlers.ml
+++ b/lib/post_handlers.ml
@@ -1,4 +1,5 @@
open Piaf
+open Shared
type pool = ((module Rapper_helper.CONNECTION), Caqti_error.t) Caqti_eio.Pool.t
@@ -8,32 +9,47 @@ type pool = ((module Rapper_helper.CONNECTION), Caqti_error.t) Caqti_eio.Pool.t
let create_post (db_pool : pool) (request : Request.t) =
Caqti_eio.Pool.use
(fun conn ->
- (* Parse JSON body *)
- match Body.to_string request.body with
- | Error _ -> Ok (Response.create `Bad_request)
- | Ok body_str ->
- try
- let json = Yojson.Safe.from_string body_str in
- let open Yojson.Safe.Util in
- let title = json |> member "title" |> to_string in
- let content = json |> member "content" |> to_string in
- let tags = json |> member "tags" |> to_string_option |> Option.value ~default:"" in
- let url = json |> member "url" |> to_string_option |> Option.value ~default:"" in
- let date = Unix.time () |> Unix.gmtime |> fun tm ->
- Printf.sprintf "%04d-%02d-%02d %02d:%02d:%02d"
- (tm.tm_year + 1900) (tm.tm_mon + 1) tm.tm_mday
- tm.tm_hour tm.tm_min tm.tm_sec in
- (* Insert the post *)
- match Query.create_post ~title ~content ~date ~tags ~url conn with
- | Ok () ->
- let response_json = `Assoc ["message", `String "Post created successfully"] in
- Ok (Response.of_string ~body:(Yojson.Safe.to_string response_json) `Created)
- | Error err ->
- Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
- Ok (Response.create `Internal_server_error)
- with
- | Yojson.Json_error _ -> Ok (Response.create `Bad_request)
- | _ -> Ok (Response.create `Bad_request))
+ (* Parse JSON body *)
+ match Body.to_string request.body with
+ | Error _ -> Ok (Response.create `Bad_request)
+ | Ok body_str ->
+ (try
+ let json = Yojson.Safe.from_string body_str in
+ let open Yojson.Safe.Util in
+ let title = json |> member "title" |> to_string in
+ let content = json |> member "content" |> to_string in
+ let tags =
+ json |> member "tags" |> to_string_option |> Option.value ~default:""
+ in
+ let url =
+ json |> member "url" |> to_string_option |> Option.value ~default:""
+ in
+ let date =
+ Unix.time ()
+ |> Unix.gmtime
+ |> fun tm ->
+ Printf.sprintf
+ "%04d-%02d-%02d %02d:%02d:%02d"
+ (tm.tm_year + 1900)
+ (tm.tm_mon + 1)
+ tm.tm_mday
+ tm.tm_hour
+ tm.tm_min
+ tm.tm_sec
+ in
+ (* Insert the post *)
+ match Query.create_post ~title ~content ~date ~tags ~url conn with
+ | Ok () ->
+ let response_json =
+ `Assoc [ "message", `String "Post created successfully" ]
+ in
+ Ok (Response.of_string ~body:(Yojson.Safe.to_string response_json) `Created)
+ | Error err ->
+ Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
+ Ok (Response.create `Internal_server_error)
+ with
+ | Yojson.Json_error _ -> Ok (Response.create `Bad_request)
+ | _ -> Ok (Response.create `Bad_request)))
db_pool
;;
@@ -41,33 +57,50 @@ let create_post (db_pool : pool) (request : Request.t) =
let create_comment (db_pool : pool) (request : Request.t) =
Caqti_eio.Pool.use
(fun conn ->
- match Body.to_string request.body with
- | Error _ -> Ok (Response.create `Bad_request)
- | Ok body_str ->
- try
- let json = Yojson.Safe.from_string body_str in
- let open Yojson.Safe.Util in
- let content = json |> member "content" |> to_string in
- let post_id = json |> member "post_id" |> to_int in
- let parent = json |> member "parent" |> to_int_option in
- let author = json |> member "author" |> to_string in
- let tags = json |> member "tags" |> to_string_option |> Option.value ~default:"" in
- let url = json |> member "url" |> to_string_option |> Option.value ~default:"" in
- let date = Unix.time () |> Unix.gmtime |> fun tm ->
- Printf.sprintf "%04d-%02d-%02d %02d:%02d:%02d"
- (tm.tm_year + 1900) (tm.tm_mon + 1) tm.tm_mday
- tm.tm_hour tm.tm_min tm.tm_sec in
- (* Insert the comment *)
- match Query.create_comment ~content ~date ~tags ~url ~post_id ?parent ~author conn with
- | Ok () ->
- let response_json = `Assoc ["message", `String "Comment created successfully"] in
- Ok (Response.of_string ~body:(Yojson.Safe.to_string response_json) `Created)
- | Error err ->
- Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
- Ok (Response.create `Internal_server_error)
- with
- | Yojson.Json_error _ -> Ok (Response.create `Bad_request)
- | _ -> Ok (Response.create `Bad_request))
+ match Body.to_string request.body with
+ | Error _ -> Ok (Response.create `Bad_request)
+ | Ok body_str ->
+ (try
+ let json = Yojson.Safe.from_string body_str in
+ let open Yojson.Safe.Util in
+ let content = json |> member "content" |> to_string in
+ let post_id = json |> member "post_id" |> to_int in
+ let parent = json |> member "parent" |> to_int_option in
+ let author = json |> member "author" |> to_string in
+ let tags =
+ json |> member "tags" |> to_string_option |> Option.value ~default:""
+ in
+ let url =
+ json |> member "url" |> to_string_option |> Option.value ~default:""
+ in
+ let date =
+ Unix.time ()
+ |> Unix.gmtime
+ |> fun tm ->
+ Printf.sprintf
+ "%04d-%02d-%02d %02d:%02d:%02d"
+ (tm.tm_year + 1900)
+ (tm.tm_mon + 1)
+ tm.tm_mday
+ tm.tm_hour
+ tm.tm_min
+ tm.tm_sec
+ in
+ (* Insert the comment *)
+ match
+ Query.create_comment ~content ~date ~tags ~url ~post_id ?parent ~author conn
+ with
+ | Ok () ->
+ let response_json =
+ `Assoc [ "message", `String "Comment created successfully" ]
+ in
+ Ok (Response.of_string ~body:(Yojson.Safe.to_string response_json) `Created)
+ | Error err ->
+ Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
+ Ok (Response.create `Internal_server_error)
+ with
+ | Yojson.Json_error _ -> Ok (Response.create `Bad_request)
+ | _ -> Ok (Response.create `Bad_request)))
db_pool
;;
@@ -75,26 +108,28 @@ let create_comment (db_pool : pool) (request : Request.t) =
let create_vote (db_pool : pool) (request : Request.t) =
Caqti_eio.Pool.use
(fun conn ->
- match Body.to_string request.body with
- | Error _ -> Ok (Response.create `Bad_request)
- | Ok body_str ->
- try
- let json = Yojson.Safe.from_string body_str in
- let open Yojson.Safe.Util in
- let user_id = json |> member "user_id" |> to_string in
- let vote_type = json |> member "vote_type" |> to_string in
- let post_id = json |> member "post_id" |> to_int_option in
- let comment_id = json |> member "comment_id" |> to_int_option in
- (* Insert the vote *)
- match Query.create_vote ~user_id ~vote_type ?post_id ?comment_id conn with
- | Ok () ->
- let response_json = `Assoc ["message", `String "Vote created successfully"] in
- Ok (Response.of_string ~body:(Yojson.Safe.to_string response_json) `Created)
- | Error err ->
- Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
- Ok (Response.create `Internal_server_error)
- with
- | Yojson.Json_error _ -> Ok (Response.create `Bad_request)
- | _ -> Ok (Response.create `Bad_request))
+ match Body.to_string request.body with
+ | Error _ -> Ok (Response.create `Bad_request)
+ | Ok body_str ->
+ (try
+ let json = Yojson.Safe.from_string body_str in
+ let open Yojson.Safe.Util in
+ let user_id = json |> member "user_id" |> to_string in
+ let vote_type = json |> member "vote_type" |> to_string in
+ let post_id = json |> member "post_id" |> to_int_option in
+ let comment_id = json |> member "comment_id" |> to_int_option in
+ (* Insert the vote *)
+ match Query.create_vote ~user_id ~vote_type ?post_id ?comment_id conn with
+ | Ok () ->
+ let response_json =
+ `Assoc [ "message", `String "Vote created successfully" ]
+ in
+ Ok (Response.of_string ~body:(Yojson.Safe.to_string response_json) `Created)
+ | Error err ->
+ Logs.err (fun m -> m "Database error: %a" Caqti_error.pp err);
+ Ok (Response.create `Internal_server_error)
+ with
+ | Yojson.Json_error _ -> Ok (Response.create `Bad_request)
+ | _ -> Ok (Response.create `Bad_request)))
db_pool
-;; \ No newline at end of file
+;;
diff --git a/lib/router.ml b/lib/router.ml
index 4e05da7..8649e5f 100644
--- a/lib/router.ml
+++ b/lib/router.ml
@@ -56,7 +56,8 @@ let routes =
(* bs5 routes *)
(* root *)
- ; `GET, nil @--> Html.render @@ Pages.BlogIndex.make ()
+ (* ; `GET, nil @--> Html.render @@ Pages.BlogIndex.make () *)
+ ; `GET, nil @--> Html.render_conn @@ Pages.BlogIndex.make
; `GET, (s "l" /? nil) @--> Html.get_root
]
;;
@@ -64,7 +65,6 @@ let routes =
(* Transform the routes map by applying 'one_of' to each list of routes *)
(* 'one_of' combines multiple routes into a single router that tries each in order *)
let router = R.map one_of routes
-let lol = Html.render @@ Pages.BlogIndex.make ()
(* Function to match an incoming request against our routes *)
let match_route verb path =
diff --git a/lib/shared/dune b/lib/shared/dune
new file mode 100644
index 0000000..12bfe80
--- /dev/null
+++ b/lib/shared/dune
@@ -0,0 +1,8 @@
+(library
+ (name shared)
+ (libraries
+ caqti
+ caqti-eio
+ ppx_rapper_eio)
+ (preprocess
+ (pps ppx_rapper))) \ No newline at end of file
diff --git a/lib/query.ml b/lib/shared/query.ml
index 179deb6..bd1f11c 100644
--- a/lib/query.ml
+++ b/lib/shared/query.ml
@@ -1,27 +1,33 @@
+open Rapper_helper
+
+type conn = (module CONNECTION)
+type pool = ((module CONNECTION), Caqti_error.t) Caqti_eio.Pool.t
+type 'a result_promise = ('a, Caqti_error.t) result Eio.Promise.t
+
(* Define record types for the query outputs *)
-type post_summary = {
- id: int;
- title: string;
- content: string;
- date: string;
-}
-
-type post = {
- id: int;
- title: string;
- content: string;
- date: string;
- tags: string;
- url: string;
-}
-
-type comment = {
- id: int;
- content: string;
- date: string;
- tags: string;
- url: string;
-}
+type post_summary =
+ { id : int
+ ; title : string
+ ; content : string
+ ; date : string
+ }
+
+type post =
+ { id : int
+ ; title : string
+ ; content : string
+ ; date : string
+ ; tags : string
+ ; url : string
+ }
+
+type comment =
+ { id : int
+ ; content : string
+ ; date : string
+ ; tags : string
+ ; url : string
+ }
module Query = struct
let poasts =
@@ -119,28 +125,34 @@ module Query = struct
;;
end
+(* let get_poasts pool = Caqti_eio.Pool.use (Query.poasts ()) pool *)
+
let get_poasts conn = Query.poasts conn
let get_poast post_id conn = Query.poast ~post_id conn
(* Insert functions *)
-let create_post ~title ~content ~date ~tags ~url conn =
+let create_post ~title ~content ~date ~tags ~url conn =
Query.insert_post ~title ~content ~date ~tags ~url conn
+;;
let create_comment ~content ~date ~tags ~url ~post_id ?parent ~author conn =
Query.insert_comment ~content ~date ~tags ~url ~post_id ~parent ~author conn
+;;
let create_vote ~user_id ~vote_type ?post_id ?comment_id conn =
Query.insert_vote ~post_id ~comment_id ~user_id ~vote_type conn
+;;
(* SQLite performance pragmas - to be implemented *)
-let sqlite_pragmas = [
- "PRAGMA journal_mode = WAL";
- "PRAGMA foreign_keys = ON";
- "PRAGMA cache_size = -8000";
- "PRAGMA temp_store = MEMORY";
- "PRAGMA synchronous = NORMAL";
- "PRAGMA mmap_size = 30000000000";
-]
+let sqlite_pragmas =
+ [ "PRAGMA journal_mode = WAL"
+ ; "PRAGMA foreign_keys = ON"
+ ; "PRAGMA cache_size = -8000"
+ ; "PRAGMA temp_store = MEMORY"
+ ; "PRAGMA synchronous = NORMAL"
+ ; "PRAGMA mmap_size = 30000000000"
+ ]
+;;
(* Example of how to execute raw SQL with Caqti *)
let init_connection conn =
@@ -155,17 +167,20 @@ let init_connection conn =
| Ok () -> Ok ()
| Error _ ->
(* If that fails, try as a query that returns a string *)
- match C.find_opt ((unit ->? string) sql) () with
- | Ok _ -> Ok ()
- | Error _ ->
- (* If that also fails, try as a query that returns an int *)
- match C.find_opt ((unit ->? int) sql) () with
- | Ok _ -> Ok ()
- | Error e -> Error e
+ (match C.find_opt ((unit ->? string) sql) () with
+ | Ok _ -> Ok ()
+ | Error _ ->
+ (* If that also fails, try as a query that returns an int *)
+ (match C.find_opt ((unit ->? int) sql) () with
+ | Ok _ -> Ok ()
+ | Error e -> Error e))
in
(* Execute each pragma *)
- List.fold_left (fun acc sql ->
- match acc with
- | Error e -> Error e
- | Ok () -> exec_pragma sql
- ) (Ok ()) sqlite_pragmas
+ List.fold_left
+ (fun acc sql ->
+ match acc with
+ | Error e -> Error e
+ | Ok () -> exec_pragma sql)
+ (Ok ())
+ sqlite_pragmas
+;;