diff --git a/Dockerfile b/Dockerfile index 7f667f7..cc3d0fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,9 +6,13 @@ WORKDIR /home/opam/website_monitor RUN sudo apt-get update && sudo apt-get install -y \ pkg-config \ libssl-dev \ + libgmp-dev \ + libev-dev \ + libpq-dev \ ca-certificates \ m4 \ postgresql-client \ + wget \ && sudo rm -rf /var/lib/apt/lists/* # Copy project files @@ -38,6 +42,7 @@ RUN apt-get update && apt-get install -y \ libssl3 \ ca-certificates \ tzdata \ + wget \ && rm -rf /var/lib/apt/lists/* # Copy binaries from build stage diff --git a/bin/main.ml b/bin/main.ml index 20af79e..67e4460 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -4,10 +4,17 @@ open Dream open Lwt.Infix let () = - let env = Dream.run ~interface:"0.0.0.0" ~port:8080 @@ fun _ -> - (* CORS middleware *) - let cors = - Dream.middleware + (* Create database pool *) + ignore (Database.create_pool ()); + + (* Initialize database schema *) + Lwt.catch + (fun () -> Database.init_schema ()) + (fun _exn -> Lwt.return_unit) (* Continue even if init fails *) + + (* CORS middleware *) + let cors = + Dream.middleware @@ fun next req -> let origin = Dream.header "Origin" req |> Option.value ~default:"*" in Dream.respond_with_headers @@ -18,23 +25,23 @@ let () = ("Access-Control-Allow-Credentials", "true"); ] @@ fun res -> next req res - in + in - (* Logging middleware *) - let logger = - Dream.middleware + (* Logging middleware *) + let logger = + Dream.middleware @@ fun next req -> Lwt.finalize (fun () -> Logs.app (fun m -> m "%s %s" (Dream.method_str req) (Dream.target req)); next req) (fun () -> Lwt.return_unit) - in + in - (* Routes *) - let router = - Dream.group - [ + (* Routes *) + let router = + Dream.group + [ (* Health check *) Dream.get "/health" @@ fun _ -> Lwt.return @@ Dream.json `Ok (Yojson.Basic.(assoc ["status", `String "healthy"])); @@ -64,7 +71,7 @@ let () = Dream.get "/stats/summary" Website_monitor_api.get_stats_summary; ]); - (* Admin dashboard routes - server-side rendered with server-reason-react *) + (* Admin dashboard routes *) Dream.get "/" Website_monitor_ui.serve_dashboard; Dream.get "/dashboard" Website_monitor_ui.serve_dashboard; Dream.get "/dashboard/websites" Website_monitor_ui.serve_websites_page; @@ -74,18 +81,14 @@ let () = (* Static assets *) Dream.get "/static/*" (Dream.static ~loader:(Dream.filesystem "") ""); ] - in - - (* Apply middlewares and router *) - Dream.logger ~level:`Debug - @@ cors - @@ logger - @@ router - in (* Start monitoring scheduler *) Website_monitor_scheduler.start (); (* Run the server *) - env + Dream.run ~interface:"0.0.0.0" ~port:8080 + @@ Dream.logger ~level:`Debug + @@ cors + @@ logger + @@ router diff --git a/dune-project b/dune-project index 9a91223..849681c 100644 --- a/dune-project +++ b/dune-project @@ -20,5 +20,13 @@ (ocaml-protoc-plugin (>= 8.0)) (cohttp-lwt-unix (>= 5.0)) (ocaml-ssl (>= 0.7)) - calendar) + ptime + ptime-lwt + fmt + logs + logs-fmt + angstrom + base64 + ipaddr + cmdliner) (license MIT)) diff --git a/lib/database.ml b/lib/database.ml index 08a2e02..c5a84a0 100644 --- a/lib/database.ml +++ b/lib/database.ml @@ -4,14 +4,22 @@ open Lwt.Infix open Caqti_type (* Database connection pool *) +let pool = ref None let pool_size = 5 -(* Database URL from environment *) -let db_url = +(* Get database URL from environment *) +let db_url () = try Sys.getenv "DATABASE_URL" with Not_found -> "postgresql://monitor_user:changeme@localhost:5432/website_monitor" +(* Create database pool *) +let create_pool () = + let driver = Caqti_lwt.connect (Caqti_driver_postgres.connect ()) in + let uri = Caqti_uri.of_string_exn (db_url ()) in + let p = Caqti_pool.create ~max_size:pool_size driver uri in + pool := Some p + (* Website model *) module Website = struct type t = { @@ -49,6 +57,22 @@ module Website = struct created_at; updated_at; last_checked; last_status } end +(* Get database connection pool *) +let get_pool () = + match !pool with + | Some p -> p + | None -> failwith "Database pool not initialized" + +(* Set database pool (called by Dream middleware) *) +let set_pool p = + pool := Some p + +(* Initialize database pool *) +let initialize_pool () = + () + +(* Note: When using Dream.sql_pool, the pool is passed via Dream.sql *) + (* Alert model *) module Alert = struct type t = { @@ -101,12 +125,6 @@ module CheckHistory = struct { id; website_id; status_code; response_time; error_message; checked_at } end -(* Database connection pool *) -let pool = - let driver = Caqti_block.connect (Caqti_driver_postgres.connect ()) in - let uri = Caqti_uri.of_string_exn db_url in - Caqti_pool.create ~max_size:pool_size driver uri - (* Initialize database schema *) let init_schema () = let queries = @@ -114,7 +132,8 @@ let init_schema () = Alerts.create_table; CheckHistories.create_table |] in - Lwt_list.iter_s (fun q -> Caqti_request.exec pool q ()) queries + let p = get_pool () in + Lwt_list.iter_s (fun q -> Caqti_request.exec p q ()) queries >>= fun () -> Logs.app (fun m -> m "Database schema initialized"); Lwt.return_unit diff --git a/website_monitor.opam b/website_monitor.opam index 7bec3e0..7fa4d43 100644 --- a/website_monitor.opam +++ b/website_monitor.opam @@ -9,24 +9,18 @@ bug-reports: "https://github.com/username/website_monitor/issues" depends: [ "ocaml" {>= "5.0"} "dune" {>= "3.11"} - "dream" {>= "1.0.0"} - "reason" {>= "3.8"} - "server-reason-react" {>= "5.0"} + "dream" "caqti" {>= "2.1"} - "caqti-dream" {>= "2.1"} + "caqti-lwt" "lwt" {>= "5.6"} "lwt_ppx" "yojson" {>= "2.1"} - "ocaml-protoc-plugin" {>= "8.0"} "cohttp-lwt-unix" {>= "5.0"} - "ocaml-ssl" {>= "0.7"} - "calendar" {>= "2.4"} "cmdliner" "ipaddr" "ptime" "fmt" "logs" {>= "0.7"} - "logs-fmt" "angstrom" "base64" ] @@ -35,7 +29,3 @@ build: [ ["dune" "build" "-p" name "-j" jobs] ] dev-repo: "git+https://github.com/username/website_monitor.git" -url { - src: "https://github.com/username/website_monitor/archive/refs/tags/v1.0.0.tar.gz" - checksum: "md5=dummy-checksum" -}