Please paste in a git repository below:
10 |├── .DS_Store ├── .github └── workflows │ └── setup.yml ├── .gitignore ├── .vscode └── settings.json ├── Makefile ├── README.md ├── client ├── client.ml ├── dune ├── lib │ ├── dune │ ├── shared.ml │ └── shared.mli └── repo_data.ml ├── dune ├── dune-project ├── server ├── api │ ├── app.ml │ ├── app.mli │ └── dune ├── dune └── server.eml.ml ├── simple_mini_github.opam └── static └── style.css /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinakajoy/mini_github_app_using_ocaml/6aecc2c93981c1f19f3b50bdd8ebf255560d1c50/.DS_Store -------------------------------------------------------------------------------- /.github/workflows/setup.yml: -------------------------------------------------------------------------------- 1 | name: simple_mini_github 2 | on: [push] 3 | 4 | jobs: 5 | build: 6 | strategy: 7 | fail-fast: false 8 | matrix: 9 | os: 10 | - ubuntu-latest 11 | ocaml-compiler: 12 | - 4.13.1 13 | 14 | runs-on: ${{ matrix.os }} 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | 20 | - name: Use OCaml ${{ matrix.ocaml-compiler }} 21 | uses: ocaml/setup-ocaml@v2 22 | with: 23 | ocaml-compiler: ${{ matrix.ocaml-compiler }} 24 | 25 | - name: Install project dependencies 26 | run: opam install . --deps-only 27 | 28 | - name: Install project dependencies 29 | run: opam exec -- dune build 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | repos 3 | static/client.js 4 | static/repo_data.js 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ocaml.sandbox": { 3 | "kind": "opam", 4 | "switch": "4.13.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | opam install . --deps-only 3 | 4 | build: 5 | rm -rf repos/ 6 | dune build 7 | 8 | start: 9 | rm -rf repos/ 10 | dune build 11 | dune exec ./server/server.exe 12 | 13 | run-server-only: 14 | rm -rf repos/ 15 | dune exec ./server/server.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple_mini_github 2 | 3 | A simple app to test out `irmin-graphql`, `dream` and `js_of_ocaml` 4 | 5 | ## How to setup the project 6 | - clone the repository 7 | - Run `make install` to install all project dependencies 8 | - Run `make start` to start the application 9 | - Open `localhost:8080` in the browser to view the project frontend 10 | - Open `http://localhost:8080/graphiql` in the browser to view graphiql 11 | 12 | ## To test 13 | - Paste in a github repository in the input box on the frontend 14 | - Run this sample query on graphiql to see result 15 | ``` 16 | { 17 | branches { 18 | name 19 | head { 20 | info { 21 | date 22 | author 23 | message 24 | } 25 | } 26 | } 27 | master { 28 | name 29 | tree { 30 | get_contents(key: "README.md") { 31 | key 32 | metadata 33 | value 34 | hash 35 | } 36 | } 37 | } 38 | } 39 | ``` -------------------------------------------------------------------------------- /client/client.ml: -------------------------------------------------------------------------------- 1 | open Brr 2 | 3 | let submit_btn = (Document.find_el_by_id G.document) (Jstr.v "submit") 4 | 5 | let format_result data = 6 | match data with 7 | | Some _ -> Window.set_location G.window (Uri.v (Jstr.v "http://localhost:8080/repo-data")) 8 | | None -> Shared.display_text "There was an error" 9 | 10 | let save_repo req_body = 11 | Shared.display_text ""; 12 | let url = "http://localhost:8080/repo" in 13 | let req_body = Jstr.to_string (Jstr.lowercased (Jstr.of_string req_body)) in 14 | Shared.post_data url req_body 15 | 16 | let set_repo _ = 17 | let input = (Document.find_el_by_id G.document) (Jstr.v "input") in 18 | match input with 19 | | Some element -> 20 | let users_repo = Jstr.to_string (El.prop El.Prop.value element) in 21 | if (Jstr.is_empty (Jstr.of_string users_repo)) 22 | then Shared.display_text "Please paste in a valid git repository" 23 | else 24 | let result = save_repo users_repo in 25 | Fut.await result format_result; 26 | | None -> () 27 | 28 | let () = 29 | Shared.set_date (); 30 | let submit = submit_btn in 31 | match submit with 32 | | Some el -> ignore(Ev.listen Ev.click set_repo (El.as_target el)); 33 | | None -> () 34 | -------------------------------------------------------------------------------- /client/dune: -------------------------------------------------------------------------------- 1 | (executables 2 | (names client repo_data) 3 | (modes js) 4 | (libraries shared js_of_ocaml-lwt yojson brr omd) 5 | (preprocess (pps js_of_ocaml-ppx))) -------------------------------------------------------------------------------- /client/lib/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name shared) 3 | (libraries js_of_ocaml-lwt brr)) -------------------------------------------------------------------------------- /client/lib/shared.ml: -------------------------------------------------------------------------------- 1 | open Brr 2 | open Brr_io 3 | open Fut.Syntax 4 | 5 | let set_date () = 6 | let date_span = (Document.find_el_by_id G.document) (Jstr.v "date") in 7 | match date_span with 8 | | Some v -> El.set_prop (El.Prop.jstr (Jstr.v "innerHTML")) (Jstr.v "2022") v; 9 | | None -> () 10 | 11 | let display_text result = 12 | let result_element = (Document.find_el_by_id G.document) (Jstr.v "result") in 13 | match result_element with 14 | | Some v -> El.set_prop (El.Prop.jstr (Jstr.v "innerHTML")) (Jstr.v result) v 15 | | None -> () 16 | 17 | let display_element result = 18 | let result_element = (Document.find_el_by_id G.document) (Jstr.v "result") in 19 | match result_element with 20 | | Some v -> El.append_children v result 21 | | None -> () 22 | 23 | let get_response_data response = 24 | let* data = Fetch.Body.text (Fetch.Response.as_body response) in 25 | match data with 26 | | Ok response -> Fut.return (Some response) 27 | | Error error -> 28 | Console.error [ Jstr.v "Error!", Jv.Error.message error ]; 29 | Fut.return None 30 | 31 | let post_data url req_query = 32 | let req_url = Jstr.of_string url in 33 | let req_query = Jstr.of_string req_query in 34 | let init = 35 | Fetch.Request.init 36 | ~method':(Jstr.of_string "POST") 37 | ~body:(Fetch.Body.of_jstr req_query) 38 | ~headers: 39 | (Fetch.Headers.of_assoc 40 | [ Jstr.of_string "Content-Type", Jstr.of_string "application/json" ]) 41 | () 42 | in 43 | let* result = Fetch.url ~init req_url in 44 | match result with 45 | | Ok response -> get_response_data response 46 | | Error error -> 47 | Console.error [ Jstr.v "Err!", Jv.Error.message error ]; 48 | Fut.return None -------------------------------------------------------------------------------- /client/lib/shared.mli: -------------------------------------------------------------------------------- 1 | open Brr 2 | 3 | val set_date: unit -> unit 4 | 5 | val display_text : string -> unit 6 | 7 | val display_element : El.t list -> unit 8 | 9 | val post_data: string -> string -> Jstr.t option Fut.t -------------------------------------------------------------------------------- /client/repo_data.ml: -------------------------------------------------------------------------------- 1 | open Brr 2 | open Yojson.Basic 3 | 4 | let get_string key l = 5 | match List.assoc key l with `String s -> s | _ -> raise Not_found 6 | 7 | let find t path = 8 | let rec aux j p = match p, j with 9 | | [], j -> j 10 | | h::tl, `Assoc o -> aux (List.assoc h o) tl 11 | | _ -> raise Not_found in 12 | aux t path 13 | 14 | let display_data data text = 15 | let span = El.span [El.txt (Jstr.v text)] in 16 | match data with 17 | | `String mydata -> 18 | let elem_class = Jstr.v "myclass" in 19 | let content = [El.txt (Jstr.v mydata)] in 20 | let h2 = El.h2 ~at:At.[class' elem_class] content in 21 | let div = El.div ~at:At.[class' (Jstr.v "wrapper")] [] in 22 | El.append_children div [span; h2]; 23 | Shared.display_element [div] 24 | | _ -> 25 | let txt = "Error! Expected a string!" in 26 | let elem_class = Jstr.v "error" in 27 | let p_content = [El.txt (Jstr.v txt)] in 28 | let p = El.p ~at:At.[class' elem_class] p_content in 29 | let div = El.div ~at:At.[class' (Jstr.v "wrapper")] [] in 30 | El.append_children div [span; p]; 31 | Shared.display_element [div] 32 | 33 | let style_result repo_data = 34 | let data = Jstr.to_string repo_data in 35 | let json = from_string data in 36 | let branches_json = find json [ "data"; "branches" ] in 37 | match branches_json with 38 | | `List [] -> Shared.display_text "Empty branch(es)!" 39 | | `List (branch :: _) -> 40 | begin 41 | let date = find branch [ "head"; "info"; "date" ] in 42 | display_data date "Date: "; 43 | let author = find branch [ "head"; "info"; "author" ] in 44 | display_data author "Author: "; 45 | let message = find branch [ "head"; "info"; "message" ] in 46 | display_data message "Message: " 47 | end 48 | | _ -> Shared.display_text "Error! Expected a list and got something else" 49 | 50 | let style_result2 repo_data = 51 | let data = Jstr.to_string repo_data in 52 | let json = from_string data in 53 | let readme = find json [ "data"; "main"; "tree"; "get_contents"; "value" ] in 54 | match readme with 55 | | `String s -> 56 | let elem_class = Jstr.v "readme" in 57 | let p_content = [El.txt (Jstr.v s)] in 58 | let p = El.p ~at:At.[class' elem_class] p_content in 59 | Shared.display_element [p] 60 | | _ -> Shared.display_text "There was an error" 61 | 62 | let format_result data = 63 | match data with 64 | | Some data -> 65 | style_result data; 66 | style_result2 data 67 | | None -> Shared.display_text "There was an error" 68 | 69 | let repo_query = 70 | let query = {| 71 | { 72 | branches { 73 | name 74 | head { 75 | info { 76 | date 77 | author 78 | message 79 | } 80 | } 81 | } 82 | main { 83 | tree { 84 | get_contents(path:"/README.md") 85 | metadata 86 | value 87 | hash 88 | } 89 | } 90 | } 91 | } 92 | |} 93 | in 94 | Yojson.Safe.to_string (`Assoc [ "query", `String query ]) 95 | 96 | let branches () = 97 | Shared.display_text ""; 98 | let url = "http://localhost:8080/graphql" in 99 | let result = Shared.post_data url repo_query in 100 | Fut.await result format_result 101 | 102 | let () = 103 | Shared.set_date (); 104 | branches () 105 | -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (subdir static/ 2 | (rule 3 | (deps ../client/client.bc.js) 4 | (targets client.js) 5 | (mode promote) 6 | (action (copy %{deps} %{targets}))) 7 | (rule 8 | (deps ../client/repo_data.bc.js) 9 | (targets repo_data.js) 10 | (mode promote) 11 | (action (copy %{deps} %{targets})))) 12 | 13 | (data_only_dirs repo) -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.8) 2 | -------------------------------------------------------------------------------- /server/api/app.ml: -------------------------------------------------------------------------------- 1 | open Lwt.Syntax 2 | 3 | (* Irmin store with string contents *) 4 | module Store = Irmin_git_unix.FS.KV (Irmin.Contents.String) 5 | 6 | (* Module to synchronize local store with remote store *) 7 | module Sync = Irmin.Sync.Make (Store) 8 | 9 | module Config = struct 10 | type info = Sync.info 11 | let remote = None 12 | 13 | let info = Irmin_unix.info 14 | end 15 | 16 | module Graphql_Server = Irmin_graphql.Server.Make (Cohttp_lwt_unix.Server) (Config) (Store) 17 | 18 | let store_location = "./repos" 19 | 20 | let config = Irmin_git.config ~bare:true store_location 21 | 22 | (* There must only exist a single repository that we manipulate in order for 23 | Grahpql schema to work. *) 24 | let repo () = Store.Repo.v config 25 | 26 | let check_status = 27 | Dream.log "Sync status: %a" (Fmt.result ~ok:Sync.pp_status ~error:Sync.pp_pull_error) 28 | 29 | (* Here we clear the underlying git store in preparation for a [Sync.pull], this ensures 30 | the repository is empty before the pull otherwise if a different remote is given it will 31 | not work. *) 32 | let clear_repo () = 33 | let* store = Store.Git.v (Fpath.v store_location) in 34 | match store with 35 | | Ok t -> 36 | let* status = Store.Git.reset t in 37 | Dream.log "Reset status: %a" (Fmt.result ~ok:(Fmt.any "success") ~error:Store.Git.pp_error) status; 38 | Lwt.return_unit 39 | | Error err -> 40 | Dream.log "Store error: %a" Store.Git.pp_error err; 41 | failwith "err" 42 | 43 | (* Syncing resets the repository and pulls in the new data from the specified remote. *) 44 | let sync repo path = 45 | let* () = clear_repo () in 46 | let* t = Store.main repo in 47 | let* remote = Store.remote path in 48 | let* status = Sync.pull t remote `Set in 49 | check_status status; 50 | let+ readme = Store.get t [ "README.md" ] in 51 | Printf.printf "%s\n%!" readme; 52 | "Done" 53 | 54 | (* To test why sync is not working *) 55 | let info = Irmin_git_unix.info 56 | let path2 = "git@github.com:dinakajoy/finance-tracker-app.git" 57 | 58 | let test () = 59 | let config2 = Irmin_git.config ~bare:true store_location in 60 | let* repo2= Store.Repo.v config2 in 61 | let* remote = Store.remote path2 in 62 | let* t = Store.main repo2 in 63 | let* _ = Sync.pull_exn t remote `Set in 64 | let* readme = Store.get t [ "README.md" ] in 65 | let* tree = Store.get_tree t [] in 66 | let* tree = Store.Tree.add tree [ "BAR.md" ] "Hoho!" in 67 | let* tree = Store.Tree.add tree [ "FOO.md" ] "Hihi!" in 68 | let+ () = Store.set_tree_exn t ~info:(info "merge") [] tree in 69 | Printf.printf "%s\n%!" readme 70 | 71 | let schema repo = 72 | Lwt.return @@ Graphql_Server.schema repo 73 | 74 | let () = Lwt_main.run (test ()) -------------------------------------------------------------------------------- /server/api/app.mli: -------------------------------------------------------------------------------- 1 | module Store: Irmin_git.S 2 | 3 | val repo : unit -> Store.repo Lwt.t 4 | (** Initialise the repository for storing the Github repo *) 5 | 6 | val sync : Store.repo -> string -> string Lwt.t 7 | (** [sync repo url] syncs the repository at [url] with the local store. *) 8 | 9 | val schema : Store.repo -> unit Graphql_lwt.Schema.schema Lwt.t 10 | (** This schema allows to query and modify a git repository. *) -------------------------------------------------------------------------------- /server/api/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name app) 3 | (libraries dream irmin-git.unix irmin-graphql.unix cohttp-lwt-unix lwt) 4 | (preprocess 5 | (pps ppx_irmin))) -------------------------------------------------------------------------------- /server/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name server) 3 | (libraries app dream)) 4 | 5 | (rule 6 | (targets server.ml) 7 | (deps server.eml.ml) 8 | (action (run dream_eml %{deps} --workspace %{workspace_root}))) 9 | -------------------------------------------------------------------------------- /server/server.eml.ml: -------------------------------------------------------------------------------- 1 | open Lwt.Syntax 2 | 3 | let home = 4 | 5 |
6 | 7 |Please paste in a git repository below:
10 |