pam
This commit is contained in:
parent
6387ed26d8
commit
7570f5a94c
13 changed files with 307 additions and 0 deletions
37
pam/bin/api.ml
Normal file
37
pam/bin/api.ml
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
open Yojson.Safe
|
||||||
|
|
||||||
|
let url = "https://bugs.lezzo.org/api/v1/repos/bparodi/Documenti/issues?state=open&type=issues"
|
||||||
|
|
||||||
|
let headers =
|
||||||
|
let base64password = Datatypes.forgejo in
|
||||||
|
[("accept", "application/json");
|
||||||
|
("authorization", [%string "Basic %{base64password}"])]
|
||||||
|
|
||||||
|
type issue = {
|
||||||
|
url: string;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
due_date: string option;
|
||||||
|
}
|
||||||
|
and issues = issue list
|
||||||
|
|
||||||
|
let issue_of_json json =
|
||||||
|
let open Yojson.Safe.Util in
|
||||||
|
{
|
||||||
|
url = json |> member "url" |> to_string;
|
||||||
|
title = json |> member "title" |> to_string;
|
||||||
|
body = json |> member "body" |> to_string;
|
||||||
|
due_date = json |> member "due_date" |> to_option to_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let string_of_issue issue =
|
||||||
|
let due_date_str = match issue.due_date with
|
||||||
|
| Some date -> date
|
||||||
|
| None -> ""
|
||||||
|
in
|
||||||
|
[%string {|{ url="%{issue.url}"; title="%{issue.title}"; body = "%{issue.body}"; due_date=%{due_date_str} }|}]
|
||||||
|
|
||||||
|
let issues_of_json json_str =
|
||||||
|
let open Yojson.Safe.Util in
|
||||||
|
try json_str |> from_string |> to_list |> List.map issue_of_json |> Result.ok
|
||||||
|
with | Yojson.Json_error msg -> Error [%string "JSON parsing error: %{msg}"]
|
3
pam/bin/config.ml
Normal file
3
pam/bin/config.ml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
let configuration () =
|
||||||
|
let conf = Otoml.Parser.from_file "/etc/lanonna.toml" in
|
||||||
|
Otoml.find conf Otoml.get_string ["lanonna"; "mq_url"]
|
19
pam/bin/datatypes.ml
Normal file
19
pam/bin/datatypes.ml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
type reminder = { (* from an issue in forgejo get a reminder *)
|
||||||
|
url: string;
|
||||||
|
title: string;
|
||||||
|
due_date: string ;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Riot.Message.t +=
|
||||||
|
| ReceivedFromMq of string
|
||||||
|
| WebReq
|
||||||
|
| ForgejoIssues of reminder list
|
||||||
|
| ForgejoError of string
|
||||||
|
| InternalFailure of string
|
||||||
|
|
||||||
|
let forgejo_issues lst = ForgejoIssues lst
|
||||||
|
let forgejo_error reason = ForgejoError reason
|
||||||
|
|
||||||
|
let of_reminder r =
|
||||||
|
[%string "%{r.title}"]
|
7
pam/bin/dune
Normal file
7
pam/bin/dune
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
(executable
|
||||||
|
(public_name pam)
|
||||||
|
(name main)
|
||||||
|
(libraries riot amqp-client-lwt pyml yojson batteries otoml )
|
||||||
|
(preprocess
|
||||||
|
(pps lwt_ppx ppx_string ))
|
||||||
|
)
|
31
pam/bin/httpclient.ml
Normal file
31
pam/bin/httpclient.ml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
open Pyops
|
||||||
|
open Pytypes
|
||||||
|
open Batteries
|
||||||
|
|
||||||
|
type http_actor = {requests: pyobject; url: pyobject; headers: pyobject}
|
||||||
|
|
||||||
|
let reminder_of_issue : Api.issue -> Datatypes.reminder option = function
|
||||||
|
| {due_date=None; _} -> None
|
||||||
|
| {url=url; title=title; due_date=Some due_date; body=_} -> Some {url=url; title=title; due_date=due_date}
|
||||||
|
|
||||||
|
let init () =
|
||||||
|
let _ = Py.initialize () in
|
||||||
|
let requests = Py.import "requests" in
|
||||||
|
let url = Py.String.of_string Api.url in
|
||||||
|
let headers =
|
||||||
|
Api.headers
|
||||||
|
|> List.map (fun (k, v) -> (k, Py.String.of_string v))
|
||||||
|
|> Py.Dict.of_bindings_string in
|
||||||
|
|
||||||
|
{requests=requests; url=url; headers=headers}
|
||||||
|
|
||||||
|
let make_get_request {requests; url; headers} =
|
||||||
|
let get = Py.Module.get_function_with_keywords requests "get" in
|
||||||
|
let resp = get [|url|] [("headers", headers)] in
|
||||||
|
let jsontext =
|
||||||
|
resp.@$("text")
|
||||||
|
|> Py.String.to_string
|
||||||
|
in
|
||||||
|
Api.issues_of_json jsontext
|
||||||
|
|> Result.map (Datatypes.forgejo_issues % List.filter_map reminder_of_issue)
|
||||||
|
|> Result.map_error Datatypes.forgejo_error (*TODO: maybe not really a forgejo error, more like internal *)
|
77
pam/bin/main.ml
Normal file
77
pam/bin/main.ml
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
open Riot
|
||||||
|
open Datatypes
|
||||||
|
open Batteries
|
||||||
|
open Util
|
||||||
|
|
||||||
|
let http mq_pid =
|
||||||
|
let http_actor = Httpclient.init () in
|
||||||
|
let _ = print_endline "Initialized http client" in
|
||||||
|
|
||||||
|
let rec loop () =
|
||||||
|
let _ =
|
||||||
|
match Riot.receive () with
|
||||||
|
| WebReq ->
|
||||||
|
let _ = print_endline "Got webreq" in
|
||||||
|
let reminders = Httpclient.make_get_request http_actor in
|
||||||
|
let msg =
|
||||||
|
reminders
|
||||||
|
|> Result.map (fun f -> Batteries.dump f |> print_endline; f)
|
||||||
|
|> result_unpack
|
||||||
|
in
|
||||||
|
Riot.send mq_pid msg
|
||||||
|
| _ -> failwith "Unknown msg"
|
||||||
|
in
|
||||||
|
loop ()
|
||||||
|
in loop ()
|
||||||
|
|
||||||
|
let rabbit consumer =
|
||||||
|
let%lwt mq = Mq.init consumer in
|
||||||
|
|
||||||
|
let rec loop () =
|
||||||
|
let%lwt _ = Lwt_unix.sleep 1.0 in
|
||||||
|
let _ =
|
||||||
|
try%lwt
|
||||||
|
match Riot.receive ~after:1000L () with (* TODO: Somehow the 1000L doesn't work. Report it.*)
|
||||||
|
| ForgejoError err ->
|
||||||
|
let _ = print_endline [%string "Got error from Forgejo: %{err}"] in
|
||||||
|
Lwt.return (Mq.mq_publish mq err )
|
||||||
|
| ForgejoIssues reminders ->
|
||||||
|
let _ = [%string "Got reminders: %{Batteries.dump reminders}"] |> print_endline in
|
||||||
|
let rems = List.map of_reminder reminders in
|
||||||
|
Lwt.return (Mq.mq_publish_all mq rems)
|
||||||
|
| _ ->
|
||||||
|
failwith "Unhandled msg"
|
||||||
|
with | Riot.Receive_timeout -> failwith "dunno"
|
||||||
|
|
||||||
|
in
|
||||||
|
loop ()
|
||||||
|
in
|
||||||
|
loop ()
|
||||||
|
|
||||||
|
let loop http_pid =
|
||||||
|
let rec loop_ () =
|
||||||
|
let _ = send http_pid WebReq in
|
||||||
|
let _ = sleep 60.0 in
|
||||||
|
loop_ ()
|
||||||
|
in
|
||||||
|
loop_()
|
||||||
|
|
||||||
|
let main () =
|
||||||
|
let _own = self () in
|
||||||
|
let http = spawn (fun () -> http _own) in (*TODO: nope*)
|
||||||
|
let _mq = spawn (fun () -> Lwt_main.run (rabbit http)) in
|
||||||
|
sleep 2.0
|
||||||
|
;
|
||||||
|
send _mq (Datatypes.ForgejoIssues []);
|
||||||
|
let _ = spawn (fun () -> loop http) in
|
||||||
|
let _ = print_endline "Now looping"
|
||||||
|
in
|
||||||
|
let rec forever () = (* TODO: is there something better? Like monitoring processes ?*)
|
||||||
|
sleep 22.2; forever ()
|
||||||
|
in forever ()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let () =
|
||||||
|
Config.configuration () |> print_endline;
|
||||||
|
Riot.run main
|
43
pam/bin/mq.ml
Normal file
43
pam/bin/mq.ml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
open Amqp_client_lwt
|
||||||
|
|
||||||
|
let handler pid { Message.message = (_content, body); _ } =
|
||||||
|
Riot.send pid (Datatypes.ReceivedFromMq ("GOT RABBITs: "^body)) (*TODO: log*)
|
||||||
|
|
||||||
|
let handle_consumer_exn _exn =
|
||||||
|
Lwt.return_unit (* TODO *)
|
||||||
|
|
||||||
|
let no_routing_key = `Queue ""
|
||||||
|
|
||||||
|
type rabbit_actor = {queue: Queue.t; channel: Channel.no_confirm Channel.t; connection: Connection.t}
|
||||||
|
|
||||||
|
let init riot_consumer =
|
||||||
|
(* Init rabbitmq client *)
|
||||||
|
let%lwt connection = Amqp.Connection.connect ~credentials:Datatypes.CREDS ~id:"Pam" MQURL in (*TODO: Creds*)
|
||||||
|
let%lwt channel = Amqp.Connection.open_channel ~id:"Pam" Amqp.Channel.no_confirm connection in
|
||||||
|
let%lwt exchange = Amqp.Exchange.declare channel Exchange.direct_t "pam" in
|
||||||
|
(* let%lwt lanonna_exchange = Amqp.Exchange.declare channel Exchange.direct_t "lanonna" in *)
|
||||||
|
let%lwt queue = Amqp.Queue.declare channel "pam" in
|
||||||
|
let _ = Amqp.Queue.bind channel queue exchange no_routing_key in
|
||||||
|
(* Amqp.Queue.publish channel queue (Amqp.Message.make "My Message Payload") >>= function `Ok -> *)
|
||||||
|
let _ = print_endline "Initialized rabbitmq client" in
|
||||||
|
|
||||||
|
(* Establish consumer *)
|
||||||
|
let%lwt (_ampq_consumer, reader) = Amqp.Queue.consume ~no_ack:true ~id:"" channel queue in
|
||||||
|
let _ = Thread.spawn ~exn_handler:handle_consumer_exn (Thread.Pipe.iter_without_pushback reader ~f:(handler riot_consumer)) in
|
||||||
|
Lwt.return {queue=queue; channel=channel; connection=connection}
|
||||||
|
|
||||||
|
let shutdown {queue=_queue; channel; connection} =
|
||||||
|
let ignore_exn = fun _ -> Lwt.return_unit in
|
||||||
|
let%lwt _ = Lwt.catch (fun () -> Amqp.Channel.close channel) ignore_exn in
|
||||||
|
let%lwt _ = Lwt.catch (fun () -> Amqp.Connection.close connection) ignore_exn in
|
||||||
|
Lwt.return_unit
|
||||||
|
|
||||||
|
let mq_publish {queue=_; channel; connection=_} msg =
|
||||||
|
let%lwt _ =
|
||||||
|
Message.make msg
|
||||||
|
|> Exchange.publish channel Exchange.default ~routing_key:"lanonna"
|
||||||
|
in Lwt.return_unit
|
||||||
|
|
||||||
|
let rec mq_publish_all mq = function
|
||||||
|
| [] -> Lwt.return_unit
|
||||||
|
| r::rest -> let%lwt _ = mq_publish mq r in mq_publish_all mq rest
|
16
pam/bin/util.ml
Normal file
16
pam/bin/util.ml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
open Unix
|
||||||
|
|
||||||
|
let now () =
|
||||||
|
let current_time = time () in
|
||||||
|
let human_time = localtime current_time in
|
||||||
|
let year = 1900 + human_time.tm_year in
|
||||||
|
let month = human_time.tm_mon + 1 in
|
||||||
|
let day = human_time.tm_mday in
|
||||||
|
let hour = human_time.tm_hour in
|
||||||
|
let minute = human_time.tm_min in
|
||||||
|
let second = human_time.tm_sec in
|
||||||
|
|
||||||
|
Printf.sprintf "%04d-%02d-%02d %02d:%02d:%02d"
|
||||||
|
year month day hour minute second
|
||||||
|
|
||||||
|
let result_unpack = function | Ok o -> o | Error e -> e
|
26
pam/dune-project
Normal file
26
pam/dune-project
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
(lang dune 3.14)
|
||||||
|
|
||||||
|
(name pam)
|
||||||
|
|
||||||
|
(generate_opam_files true)
|
||||||
|
|
||||||
|
(source
|
||||||
|
(github username/reponame))
|
||||||
|
|
||||||
|
(authors "Author Name")
|
||||||
|
|
||||||
|
(maintainers "Maintainer Name")
|
||||||
|
|
||||||
|
(license LICENSE)
|
||||||
|
|
||||||
|
(documentation https://url/to/documentation)
|
||||||
|
|
||||||
|
(package
|
||||||
|
(name pam)
|
||||||
|
(synopsis "A short synopsis")
|
||||||
|
(description "A longer description")
|
||||||
|
(depends ocaml dune)
|
||||||
|
(tags
|
||||||
|
(topics "to describe" your project)))
|
||||||
|
|
||||||
|
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
|
31
pam/pam.opam
Normal file
31
pam/pam.opam
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# This file is generated by dune, edit dune-project instead
|
||||||
|
opam-version: "2.0"
|
||||||
|
synopsis: "A short synopsis"
|
||||||
|
description: "A longer description"
|
||||||
|
maintainer: ["Maintainer Name"]
|
||||||
|
authors: ["Author Name"]
|
||||||
|
license: "LICENSE"
|
||||||
|
tags: ["topics" "to describe" "your" "project"]
|
||||||
|
homepage: "https://github.com/username/reponame"
|
||||||
|
doc: "https://url/to/documentation"
|
||||||
|
bug-reports: "https://github.com/username/reponame/issues"
|
||||||
|
depends: [
|
||||||
|
"ocaml"
|
||||||
|
"dune" {>= "3.14"}
|
||||||
|
"odoc" {with-doc}
|
||||||
|
]
|
||||||
|
build: [
|
||||||
|
["dune" "subst"] {dev}
|
||||||
|
[
|
||||||
|
"dune"
|
||||||
|
"build"
|
||||||
|
"-p"
|
||||||
|
name
|
||||||
|
"-j"
|
||||||
|
jobs
|
||||||
|
"@install"
|
||||||
|
"@runtest" {with-test}
|
||||||
|
"@doc" {with-doc}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
dev-repo: "git+https://github.com/username/reponame.git"
|
15
pam/pyproject.toml
Normal file
15
pam/pyproject.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[tool.poetry]
|
||||||
|
name = "pam"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["me"]
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.11"
|
||||||
|
pika = "^1.3.2"
|
||||||
|
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
2
pam/test/dune
Normal file
2
pam/test/dune
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(test
|
||||||
|
(name test_pam))
|
0
pam/test/test_pam.ml
Normal file
0
pam/test/test_pam.ml
Normal file
Loading…
Reference in a new issue