diff options
-rw-r--r-- | CLAUDE.md | 91 | ||||
-rw-r--r-- | dune | 2 | ||||
-rw-r--r-- | flake.nix | 7 | ||||
-rw-r--r-- | lib/router.ml | 15 |
4 files changed, 112 insertions, 3 deletions
@@ -2,11 +2,102 @@ 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. +## Current Status + +The project is successfully running as a server-side rendered React application in OCaml with the following features implemented: + +- **Database**: SQLite with schema for posts, comments, and votes +- **Routing**: Type-safe routing using the `routes` library +- **SSR React**: JSX-like syntax via mlx, server-side rendering +- **API Endpoints**: RESTful endpoints for posts, comments, and votes +- **Frontend**: Tailwind CSS styling with responsive design +- **Static Assets**: Serving CSS, images, and other static files ## Build the app To compile the app and see if the code is correct do: `dune clean && dune build` +## Running the app +```bash +# Build and run the server +dune exec bs5 + +# Or use the run script +./run.sh +``` + +## Tech Stack + +### Core +- **OCaml**: Primary language +- **Eio**: Async runtime for OCaml 5 +- **mlx**: JSX syntax for OCaml React components +- **dune**: Build system + +### Web Framework +- **Piaf**: HTTP server and client +- **React (server-side)**: Rendering React components on the server + +### Database +- **Caqti**: Database abstraction layer +- **SQLite**: Embedded database with WAL mode +- **Rapper**: Type-safe SQL queries with PPX + +### Styling +- **Tailwind CSS**: Utility-first CSS framework +- **Custom fonts**: Crimson Text and Inter fonts included + +### Development +- **Nix**: Development environment with flake.nix +- **ocaml-lsp-server**: Language server support +- **ocamlformat-mlx**: Code formatting for mlx files + +## Project Structure + +``` +├── lib/ # Core application code +│ ├── handler.ml # HTTP handlers +│ ├── router.ml # Route definitions +│ ├── html.ml # HTML rendering utilities +│ ├── post_handlers.ml # POST request handlers +│ ├── shared/ +│ │ └── query.ml # Database queries +│ └── pages/ +│ ├── BlogIndex.mlx # Main blog page React component +│ └── components/ # Reusable React components +├── assets/ # Static assets +│ ├── styles.css # Generated Tailwind CSS +│ ├── fonts/ # Web fonts +│ ├── board/ # Board/category icons +│ └── avatars/ # User avatars +├── bulkdata/ # Database files +│ ├── blog.db # SQLite database +│ └── schema.sql # Database schema +└── bin/ # Main executable +``` + +## Key Features Implemented + +### Database Schema +- **Posts**: id, title, content, date, tags, url +- **Comments**: id, content, date, tags, url, post_id, parent, author +- **Votes**: post_id, comment_id, user_id, vote_type + +### API Endpoints +- `GET /` - Main blog page with post previews +- `GET /posts` - Get all posts +- `GET /posts/:id` - Get specific post +- `GET /posts/:id/comments` - Get comments for a post +- `POST /posts` - Create new post +- `POST /comments` - Create new comment +- `POST /votes` - Create new vote +- `GET /assets/*` - Serve static assets + +### React Components +- **Layout**: Main page layout with HTML boilerplate +- **PostPreviews**: Displays list of blog post previews +- **Navbar**: Navigation component +- **SiteTitle**: Header component ## Things to take into account @@ -3,4 +3,4 @@ (deps (glob_files_rec assets/*)) (action - (bash "sh tailwind.sh"))) + (bash "sh ~/code/ocaml/bs5/tailwind.sh"))) @@ -53,8 +53,9 @@ src = pkgs.fetchFromGitHub { owner = "polwex"; repo = "server-reason-react"; - rev = "d5dd436b0a447ff0a82f9a8d7a02f102139299a9"; - sha256 = "PsrOqZgdFIy5tGoLpS+hf9uz42vKJZbSvdWRW8MX604="; + # rev = "d5dd436b0a447ff0a82f9a8d7a02f102139299a9"; + rev = "f9955158d42f87a902ff32e05629a1a4d72c1d7f"; + sha256 = "EoG6fTW2mAGge/hm0Z0mQOyxE2zIEXvRnu2KtUfWEqw="; }; propagatedBuildInputs = with pkgs.ocamlPackages; [ @@ -166,6 +167,8 @@ # }; in { devShells.default = pkgs.mkShell rec { + ANTHROPIC_BASE_URL = "https://api.moonshot.ai/anthropic"; + ANTHROPIC_AUTH_TOKEN = "sk-JXxvETDsMRTX7CP69dQZ34PTELQWVHAEk0PSxGDUh3OlnFWx"; packages = [gemini-cli]; buildInputs = nativeBuildInputs diff --git a/lib/router.ml b/lib/router.ml index 8649e5f..03e11f3 100644 --- a/lib/router.ml +++ b/lib/router.ml @@ -23,6 +23,20 @@ module R = Map.Make (struct ;; end) +let static path _db _req = + let pat = Parts.wildcard_match path in + match Body.sendfile pat with + | Error err -> Error err + | Ok body -> + (match Body.to_string body with + | Error e -> Error e + | Ok str -> Ok (Response.of_string ~body:str Status.(`Accepted))) +;; + +(* match Parts.wildcard_match path with *) +(* | "styles.css" -> Ok (Response.of_string ~body:"" Status.(`Accepted)) *) +(* | _ -> Ok (Response.of_string ~body:"" Status.(`Accepted)) *) + (* Define all routes in the application *) let routes = (* Use fold_left to build up a map of routes *) @@ -38,6 +52,7 @@ let routes = (* nil - end of path (no more segments) *) (* @--> - binds the route pattern to the handler function *) (* Handler.get_posts - the function that handles this route *) + (* ; `GET, s "assets" wildcard @--> static *) ; `GET, (s "posts" / int /? nil) @--> Handler.get_post (* / int - captures an integer parameter (post ID) *) ; `GET, (s "comments" / int /? nil) @--> Handler.get_comment |