1use crate::model::account::{Account, MAX_USERNAME_LENGTH};
2use crate::model::api::{
3 DeleteAccountRequest, GetPublicKeyRequest, GetUsernameRequest, NewAccountRequestV2,
4};
5use crate::model::errors::{LbErrKind, LbResult, core_err_unexpected};
6use crate::model::file_like::FileLike;
7use crate::model::file_metadata::{FileType, Owner};
8use crate::model::meta::Meta;
9use crate::{DEFAULT_API_LOCATION, Lb};
10use libsecp256k1::SecretKey;
11use qrcode_generator::QrCodeEcc;
12
13use crate::io::network::ApiError;
14
15impl Lb {
16 #[instrument(level = "debug", skip(self), err(Debug))]
23 pub async fn create_account(
24 &self, username: &str, api_url: &str, welcome_doc: bool,
25 ) -> LbResult<Account> {
26 let username = String::from(username).to_lowercase();
27
28 if username.len() > MAX_USERNAME_LENGTH {
29 return Err(LbErrKind::UsernameInvalid.into());
30 }
31
32 let mut tx = self.begin_tx().await;
33 let db = tx.db();
34
35 if db.account.get().is_some() {
36 return Err(LbErrKind::AccountExists.into());
37 }
38
39 let account = Account::new(username.clone(), api_url.to_string());
40
41 let root = Meta::create_root(&account)?.sign_with(&account)?;
42 let root_id = *root.id();
43
44 let last_synced = self
45 .client
46 .request(&account, NewAccountRequestV2::new(&account, &root))
47 .await?
48 .last_synced;
49
50 db.account.insert(account.clone())?;
51 db.base_metadata.insert(root_id, root)?;
52 db.last_synced.insert(last_synced as i64)?;
53 db.root.insert(root_id)?;
54 db.pub_key_lookup
55 .insert(Owner(account.public_key()), account.username.clone())?;
56
57 self.keychain.cache_account(account.clone()).await?;
58
59 tx.end();
60
61 self.events.meta_changed();
62
63 if welcome_doc {
64 let welcome_doc = self
65 .create_file("welcome.md", &root_id, FileType::Document)
66 .await?;
67 self.write_document(welcome_doc.id, Self::WELCOME_MESSAGE.as_bytes())
68 .await?;
69 self.sync(None).await?;
70 }
71
72 Ok(account)
73 }
74
75 #[instrument(level = "debug", skip(self, key), err(Debug))]
76 pub async fn import_account(&self, key: &str, api_url: Option<&str>) -> LbResult<Account> {
77 if self.get_account().is_ok() {
78 warn!("tried to import an account, but account exists already.");
79 return Err(LbErrKind::AccountExists.into());
80 }
81
82 if let Ok(key) = base64::decode(key) {
83 if let Ok(account) = bincode::deserialize(&key[..]) {
84 return self.import_account_private_key_v1(account).await;
85 } else if let Ok(key) = SecretKey::parse_slice(&key) {
86 return self
87 .import_account_private_key_v2(key, api_url.unwrap_or(DEFAULT_API_LOCATION))
88 .await;
89 }
90 }
91
92 let phrase: [&str; 24] = key
93 .split([' ', ','])
94 .filter(|maybe_word| !maybe_word.is_empty())
95 .collect::<Vec<_>>()
96 .try_into()
97 .map_err(|_| LbErrKind::AccountStringCorrupted)?;
98
99 self.import_account_phrase(phrase, api_url.unwrap_or(DEFAULT_API_LOCATION))
100 .await
101 }
102
103 pub async fn import_account_private_key_v1(&self, account: Account) -> LbResult<Account> {
104 let server_public_key = self
105 .client
106 .request(&account, GetPublicKeyRequest { username: account.username.clone() })
107 .await?
108 .key;
109
110 let account_public_key = account.public_key();
111
112 if account_public_key != server_public_key {
113 return Err(LbErrKind::UsernamePublicKeyMismatch.into());
114 }
115
116 let mut tx = self.begin_tx().await;
117 let db = tx.db();
118 db.account.insert(account.clone())?;
119 self.keychain.cache_account(account.clone()).await?;
120
121 Ok(account)
122 }
123
124 pub async fn import_account_private_key_v2(
125 &self, private_key: SecretKey, api_url: &str,
126 ) -> LbResult<Account> {
127 let mut account =
128 Account { username: "".to_string(), api_url: api_url.to_string(), private_key };
129 let public_key = account.public_key();
130
131 account.username = self
132 .client
133 .request(&account, GetUsernameRequest { key: public_key })
134 .await?
135 .username;
136
137 let mut tx = self.begin_tx().await;
138 let db = tx.db();
139 db.account.insert(account.clone())?;
140 self.keychain.cache_account(account.clone()).await?;
141
142 Ok(account)
143 }
144
145 pub async fn import_account_phrase(
146 &self, phrase: [&str; 24], api_url: &str,
147 ) -> LbResult<Account> {
148 let private_key = Account::phrase_to_private_key(phrase)?;
149 self.import_account_private_key_v2(private_key, api_url)
150 .await
151 }
152
153 #[instrument(level = "debug", skip(self), err(Debug))]
154 pub fn export_account_private_key(&self) -> LbResult<String> {
155 self.export_account_private_key_v1()
156 }
157
158 pub(crate) fn export_account_private_key_v1(&self) -> LbResult<String> {
159 let account = self.get_account()?;
160 let encoded: Vec<u8> = bincode::serialize(account).map_err(core_err_unexpected)?;
161 Ok(base64::encode(encoded))
162 }
163
164 #[allow(dead_code)]
165 pub(crate) fn export_account_private_key_v2(&self) -> LbResult<String> {
166 let account = self.get_account()?;
167 Ok(base64::encode(account.private_key.serialize()))
168 }
169
170 pub fn export_account_phrase(&self) -> LbResult<String> {
171 let account = self.get_account()?;
172 Ok(account.get_phrase()?.join(" "))
173 }
174
175 pub fn export_account_qr(&self) -> LbResult<Vec<u8>> {
176 let acct_secret = self.export_account_private_key_v1()?;
177 qrcode_generator::to_png_to_vec(acct_secret, QrCodeEcc::Low, 1024)
178 .map_err(|err| core_err_unexpected(err).into())
179 }
180
181 #[instrument(level = "debug", skip(self), err(Debug))]
182 pub async fn delete_account(&self) -> LbResult<()> {
183 let account = self.get_account()?;
184
185 self.client
186 .request(account, DeleteAccountRequest {})
187 .await
188 .map_err(|err| match err {
189 ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
190 ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
191 _ => core_err_unexpected(err),
192 })?;
193
194 let mut tx = self.begin_tx().await;
195 let db = tx.db();
196
197 db.account.clear()?;
198 db.last_synced.clear()?;
199 db.base_metadata.clear()?;
200 db.root.clear()?;
201 db.local_metadata.clear()?;
202 db.pub_key_lookup.clear()?;
203
204 Ok(())
207 }
208
209 const WELCOME_MESSAGE: &'static str = r#"# Markdown Syntax
210Markdown is a language for easily formatting your documents. This document can help you get started.
211
212## Styled Text
213To style text, wrap your text in the corresponding characters.
214| Style | Syntax | Example |
215|---------------|---------------------|-------------------|
216| emphasis | `*emphasis*` | *emphasis* |
217| strong | `**strong**` | **strong** |
218| strikethrough | `~~strikethrough~~` | ~~strikethrough~~ |
219| underline | `__underline__` | __underline__ |
220| code | ``code`` | `code` |
221| spoiler | `||spoiler||` | ||spoiler|| |
222| superscript | `^superscript^` | ^superscript^ |
223| subscript | `~subscript~` | ~subscript~ |
224
225## Links
226To make text into a link, wrap it with `[` `]`, add a link destination to the end , and wrap the destination with `(` `)`. The link destination can be a web URL or a relative path to another Lockbook file.
227```md
228[Lockbook's website](https://lockbook.net)
229```
230> [Lockbook's website](https://lockbook.net)
231
232## Images
233To embed an image, add a `!` to the beginning of the link syntax.
234```md
235
236```
237> 
238
239## Headings
240To create a heading, add up to six `#`'s plus a space before your text. More `#`'s create a smaller heading.
241```md
242# Heading 1
243## Heading 2
244### Heading 3
245#### Heading 4
246##### Heading 5
247###### Heading 6
248```
249> # Heading 1
250> ## Heading 2
251> ### Heading 3
252> #### Heading 4
253> ##### Heading 5
254> ###### Heading 6
255
256You can also create a heading using an underline composed of `=`'s (Heading 1) or `-`'s (Heading 2) on the following line.
257```md
258Heading 1
259===
260Heading 2
261---
262```
263> Heading 1
264> ===
265> Heading 2
266> ---
267
268## Lists
269Create a list item by adding `- `, `+ `, or `* ` for a bulleted list, `1. ` for a numbered list, or `- [ ] `, `+ [ ] `, or `* [ ] ` for a task list at the start of the line. The added characters are called the *list marker*.
270```md
271* bulleted list item
272- bulleted list item
273+ bulleted list item
274
2751. numbered list item
2761. numbered list item
2771. numbered list item
278
279- [ ] task list item
280- [x] task list item
281```
282>* bulleted list item
283>- bulleted list item
284>+ bulleted list item
285>
286>1. numbered list item
287>1. numbered list item
288>1. numbered list item
289>
290>- [ ] task list item
291>- [x] task list item
292
293List items can be nested. To nest an inner item in an outer one, the inner item's line must start with at least one space for each character in the outer item's list marker: usually 2 for bulleted lists, 3 for numbered lists, or 2 for tasks lists (the trailing `[ ] ` is excluded).
294```md
295* This is a bulleted list
296 * An inner item needs at least 2 spaces
2971. This is a numbered list
298 1. An inner item needs at least 3 spaces
299* [ ] This is a task list
300 * [ ] An inner item needs at least 2 spaces
301```
302> * This is a bulleted list
303> * An inner item needs at least 2 spaces
304> 1. This is a numbered list
305> 1. An inner item needs at least 3 spaces
306> * [ ] This is a task list
307> * [ ] An inner item needs at least 2 spaces
308
309List items can contain formatted content. For non-text content, each line must start with the same number of spaces as an inner list item would.
310```md
311* This item contains text,
312 > a quote
313 ### and a heading.
314* This item contains two lines of text.
315The second line doesn't need spaces.
316```
317> * This item contains text,
318> > a quote
319> ### and a heading.
320> * This item contains two lines of text.
321> The second line doesn't need spaces.
322
323## Quotes
324To create a block quote, add `> ` to each line.
325```md
326> This is a quote
327```
328> This is a quote
329
330Like list items, block quotes can contain formatted content.
331```md
332> This quote contains some text,
333> ```rust
334> // some code
335> fn main() { println!("Hello, world!"); }
336> ```
337> ### and a heading.
338
339> This quote contains two lines of text.
340The second line doesn't need added characters.
341```
342> This quote contains some text,
343> ```rust
344> // some code
345> fn main() { println!("Hello, world!"); }
346> ```
347> ### and a heading.
348
349> This quote contains two lines of text.
350The second line doesn't need added characters.
351
352## Alerts
353To create an alert, add one of 5 tags to the first line of a quote: `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, or `[!CAUTION]`. An alternate title can be added after the tag.
354```md
355> [!NOTE]
356> This is a note.
357
358> [!TIP]
359> This is a tip.
360
361> [!IMPORTANT]
362> This is important.
363
364> [!WARNING]
365> This is a warning.
366
367> [!CAUTION] Caution!!!!!
368> This is a caution.
369```
370> [!NOTE]
371> This is a note.
372
373> [!TIP]
374> This is a tip.
375
376> [!IMPORTANT]
377> This is important.
378
379> [!WARNING]
380> This is a warning.
381
382> [!CAUTION] Caution!!!!!
383> This is a caution.
384
385## Tables
386A table is written with `|`'s between columns and a row after the header row whose cell's contents are `-`'s.
387```md
388| Style | Syntax | Example |
389|---------------|---------------------|-------------------|
390| emphasis | `*emphasis*` | *emphasis* |
391| strong | `**strong**` | **strong** |
392```
393> | Style | Syntax | Example |
394> |---------------|---------------------|-------------------|
395> | emphasis | `*emphasis*` | *emphasis* |
396> | strong | `**strong**` | **strong** |
397
398## Code
399A code block is wrapped by two lines containing three backticks. A language can be added after the opening backticks.
400```md
401 ```rust
402 // some code
403 fn main() { println!("Hello, world!"); }
404 ```
405```
406> ```rust
407> // some code
408> fn main() { println!("Hello, world!"); }
409> ```
410
411You can also create a code block by indenting each line with four spaces. Indented code blocks cannot have a language.
412```md
413 // some code
414 fn main() { println!("Hello, world!"); }
415```
416> // some code
417> fn main() { println!("Hello, world!"); }
418
419## Thematic Breaks
420A thematic break is written with `***`, `---`, or `___` and shows a horizontal line across the page.
421```md
422***
423---
424___
425```
426> ***
427> ---
428> ___
429"#;
430}