summaryrefslogtreecommitdiff
path: root/bs5/universal/native/DB.ml
blob: 53ffa1b53d14639b5deba4ea72626ac145adb6a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
open Lwt.Syntax

let read_file file =
  let ( / ) = Filename.concat in
  let path = Sys.getcwd () / "server" / "db" / file in
  try%lwt
    let%lwt v = Lwt_io.with_file ~mode:Lwt_io.Input path Lwt_io.read in
    Lwt_result.return v
  with e ->
    Dream.log "Error reading file %s: %s" path (Printexc.to_string e);
    Lwt.return_error (Printexc.to_string e)

let parse_note (note : Yojson.Safe.t) : Note.t option =
  match note with
  | `Assoc fields ->
      let id =
        fields |> List.assoc "id" |> Yojson.Safe.to_string |> int_of_string
      in
      let title = fields |> List.assoc "title" |> Yojson.Safe.Util.to_string in
      let content =
        fields |> List.assoc "content" |> Yojson.Safe.Util.to_string
      in
      let updated_at =
        fields |> List.assoc "updated_at" |> Yojson.Safe.to_string
        |> float_of_string
      in
      Some { Note.id; title; content; updated_at }
  | _ -> None

let parse_notes json =
  try
    match Yojson.Safe.from_string json with
    | `List notes -> notes |> List.filter_map parse_note |> Result.ok
    | _ -> Result.error "Invalid notes file format"
  with _ -> Result.error "Invalid JSON format format"

module Cache = struct
  let db_cache = ref None
  let set value = db_cache := Some value
  let read () = !db_cache
  let delete () = db_cache := None
end

let read_notes () =
  match Cache.read () with
  | Some (Ok notes) -> Lwt_result.return notes
  | Some (Error e) -> Lwt_result.fail e
  | None -> (
      try%lwt
        match%lwt read_file "./notes.json" with
        | Ok json ->
            Cache.set (parse_notes json);
            Lwt_result.lift (parse_notes json)
        | Error _ -> Lwt.return_error "Error reading notes file"
      with _error ->
        (* When something fails, treat it as an empty note db *)
        Lwt.return_ok [])

let find_one notes id =
  match notes |> List.find_opt (fun (note : Note.t) -> note.id = id) with
  | Some note -> Lwt_result.return note
  | None -> Lwt_result.fail ("Note with id " ^ Int.to_string id ^ " not found")

let add_note ~title ~content =
  let%lwt notes = read_notes () in
  let notes =
    Result.map
      (fun notes ->
        let length = List.length notes in
        let note : Note.t =
          { id = length; title; content; updated_at = Unix.time () }
        in
        note :: notes)
      notes
  in
  Cache.set notes;
  Lwt_result.lift (notes |> Result.map (fun notes -> notes |> List.hd))

let edit_note ~id ~title ~content =
  let%lwt notes = read_notes () in
  let notes =
    Result.map
      (fun notes ->
        let notes =
          notes
          |> List.map (fun (current_note : Note.t) ->
                 if current_note.id = id then
                   {
                     current_note with
                     title;
                     content;
                     updated_at = Unix.time ();
                   }
                 else current_note)
        in
        notes)
      notes
  in
  Cache.set notes;
  Lwt_result.lift (notes |> Result.map (fun notes -> notes |> List.hd))

let delete_note id =
  let%lwt notes = read_notes () in
  let notes =
    Result.map
      (fun notes -> notes |> List.filter (fun (note : Note.t) -> note.id <> id))
      notes
  in
  Cache.set notes;
  Lwt_result.lift notes

let fetch_note id =
  match Cache.read () with
  | Some (Ok notes) -> find_one notes id
  | Some (Error e) -> Lwt_result.fail e
  | None -> (
      let* notes = read_notes () in
      match notes with
      | Ok notes -> find_one notes id
      | Error e -> Lwt_result.fail e)