1 Commits

Author SHA1 Message Date
Charles N Wyble
ed34a28c89 WIP: uncommitted changes before archiving
💘 Generated with Crush

Assisted-by: GLM-4.7 via Crush <crush@charm.land>
2026-01-13 20:14:07 -05:00
5 changed files with 70 additions and 45 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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))

View File

@@ -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

View File

@@ -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"
}