on d41c2bf help sign in
skrepka/client/Store.fs
121 lines 4.0 kB history raw
1namespace Skrepka
3open System
4open System.IO
5open LiteDB
6open Microsoft.Maui.Storage
8module Store =
10 [<CLIMutable>]
11 type Contact =
12 { [<BsonId>] Pubkey: string
13 Nickname: string }
15 type ChatMessage =
16 { Id: string
17 Body: string
18 Timestamp: DateTimeOffset
19 IsOutgoing: bool }
21 type Data =
22 { Contacts: Contact list
23 Messages: Map<string, ChatMessage list>
24 ServerUrl: string
25 PollCursor: int64 }
27 [<CLIMutable>]
28 type private MessageDoc =
29 { [<BsonId>] Id: string
30 ConversationId: string
31 Body: string
32 TimestampUnix: int64
33 IsOutgoing: bool }
35 [<CLIMutable>]
36 type private SettingsDoc =
37 { [<BsonId>] Id: string
38 ServerUrl: string
39 PollCursor: int64 }
41 let private toChatMessage (d: MessageDoc) : ChatMessage =
42 { Id = d.Id; Body = d.Body; Timestamp = DateTimeOffset.FromUnixTimeSeconds d.TimestampUnix; IsOutgoing = d.IsOutgoing }
44 let private toMessageDoc convId (m: ChatMessage) : MessageDoc =
45 { Id = m.Id; ConversationId = convId; Body = m.Body; TimestampUnix = m.Timestamp.ToUnixTimeSeconds(); IsOutgoing = m.IsOutgoing }
47 let private openDb () =
48 new LiteDatabase(Path.Combine(FileSystem.AppDataDirectory, "skrepka.db"))
50 let loadIdentity () =
51 async {
52 let! privKeyB64 =
53 SecureStorage.Default.GetAsync("identity_privkey")
54 |> Async.AwaitTask
56 let! pubKeyHex =
57 SecureStorage.Default.GetAsync("identity_pubkey")
58 |> Async.AwaitTask
60 return
61 Option.map2
62 (fun pk pub ->
63 { Crypto.Identity.PrivKey = Convert.FromBase64String pk
64 Crypto.Identity.PubKeyHex = pub })
65 (Option.ofObj privKeyB64)
66 (Option.ofObj pubKeyHex)
67 }
69 let saveIdentity (identity: Crypto.Identity) =
70 async {
71 do! SecureStorage.Default.SetAsync("identity_privkey", Convert.ToBase64String identity.PrivKey) |> Async.AwaitTask
72 do! SecureStorage.Default.SetAsync("identity_pubkey", identity.PubKeyHex) |> Async.AwaitTask
73 }
75 let loadData () : Data option =
76 try
77 use db = openDb ()
78 let settings = db.GetCollection<SettingsDoc>("settings").FindById(BsonValue "settings")
80 match settings |> Option.ofObj with
81 | None -> None
82 | Some settings ->
83 let contacts = db.GetCollection<Contact>("contacts").FindAll() |> Seq.toList
85 let messages =
86 db.GetCollection<MessageDoc>("messages").FindAll()
87 |> Seq.groupBy _.ConversationId
88 |> Seq.map (fun (convId, docs) ->
89 convId, docs |> Seq.map toChatMessage |> Seq.sortBy _.Timestamp |> Seq.toList)
90 |> Map.ofSeq
92 Some
93 { Contacts = contacts
94 Messages = messages
95 ServerUrl = settings.ServerUrl
96 PollCursor = settings.PollCursor }
97 with _ ->
98 None
100 let saveData (data: Data) =
101 try
102 use db = openDb ()
103 let contacts = db.GetCollection<Contact>("contacts")
104 let messages = db.GetCollection<MessageDoc>("messages")
105 let settings = db.GetCollection<SettingsDoc>("settings")
107 for c in data.Contacts do
108 contacts.Upsert c |> ignore
110 data.Messages
111 |> Map.iter (fun convId msgs ->
112 for m in msgs do
113 messages.Upsert(toMessageDoc convId m) |> ignore)
115 settings.Upsert
116 { Id = "settings"
117 ServerUrl = data.ServerUrl
118 PollCursor = data.PollCursor }
119 |> ignore
120 with _ ->
121 ()