diff options
author | polwex <polwex@sortug.com> | 2025-06-27 16:34:09 +0700 |
---|---|---|
committer | polwex <polwex@sortug.com> | 2025-06-27 16:34:09 +0700 |
commit | 645e815ebe11dbb86781c3eb645d3d67cd62cf7c (patch) | |
tree | 347b03391a5245de8a43198c9646719c2e892373 | |
parent | 8be9a806a93b02eada372f3993c34bc6b2f26fea (diff) |
nice nice. lsp still doesnt work tho
-rw-r--r-- | CLAUDE.md | 6 | ||||
-rw-r--r-- | bin/server.ml | 2 | ||||
-rw-r--r-- | bs5.opam | 6 | ||||
-rw-r--r-- | dune-project | 14 | ||||
-rw-r--r-- | flake.nix | 3 | ||||
-rw-r--r-- | lib/dune | 1 | ||||
-rw-r--r-- | lib/handler.ml | 232 | ||||
-rw-r--r-- | lib/html.ml | 13 | ||||
-rw-r--r-- | lib/pages/BlogIndex.mlx | 76 | ||||
-rw-r--r-- | lib/pages/BlogIndex.re | 11 | ||||
-rw-r--r-- | lib/pages/components/Navbar.mlx | 34 | ||||
-rw-r--r-- | lib/pages/components/SiteTitle.mlx | 10 | ||||
-rw-r--r-- | lib/pages/dune | 4 | ||||
-rw-r--r-- | lib/pages/lmao.re | 40 | ||||
-rw-r--r-- | lib/post_handlers.ml | 185 | ||||
-rw-r--r-- | lib/router.ml | 4 | ||||
-rw-r--r-- | lib/shared/dune | 8 | ||||
-rw-r--r-- | lib/shared/query.ml (renamed from lib/query.ml) | 103 |
18 files changed, 497 insertions, 255 deletions
@@ -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 @@ -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 @@ -107,6 +107,9 @@ findlib ocaml-lsp ocamlformat + mlx + ocamlmerlin-mlx + ocamlformat-mlx melange-json-native ppx_rapper ppx_rapper_eio @@ -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 +;; |