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
256## Lists
257Create 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*.
258```md
259* bulleted list item
260- bulleted list item
261+ bulleted list item
262
2631. numbered list item
2641. numbered list item
2651. numbered list item
266
267- [ ] task list item
268- [x] task list item
269```
270>* bulleted list item
271>- bulleted list item
272>+ bulleted list item
273>
274>1. numbered list item
275>1. numbered list item
276>1. numbered list item
277>
278>- [ ] task list item
279>- [x] task list item
280
281List 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).
282```md
283* This is a bulleted list
284 * An inner item needs at least 2 spaces
2851. This is a numbered list
286 1. An inner item needs at least 3 spaces
287* [ ] This is a task list
288 * [ ] An inner item needs at least 2 spaces
289```
290> * This is a bulleted list
291> * An inner item needs at least 2 spaces
292> 1. This is a numbered list
293> 1. An inner item needs at least 3 spaces
294> * [ ] This is a task list
295> * [ ] An inner item needs at least 2 spaces
296
297List 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.
298```md
299* This item contains text,
300 > a quote
301 ### and a heading.
302* This item contains two lines of text.
303The second line doesn't need spaces.
304```
305> * This item contains text,
306> > a quote
307> ### and a heading.
308> * This item contains two lines of text.
309> The second line doesn't need spaces.
310
311## Quotes
312To create a block quote, add `> ` to each line.
313```md
314> This is a quote
315```
316> This is a quote
317
318Like list items, block quotes can contain formatted content.
319```md
320> This quote contains some text,
321> ```rust
322> // some code
323> fn main() { println!("Hello, world!"); }
324> ```
325> ### and a heading.
326
327> This quote contains two lines of text.
328The second line doesn't need added characters.
329```
330> This quote contains some text,
331> ```rust
332> // some code
333> fn main() { println!("Hello, world!"); }
334> ```
335> ### and a heading.
336
337> This quote contains two lines of text.
338The second line doesn't need added characters.
339
340## Alerts
341To 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.
342```md
343> [!NOTE]
344> This is a note.
345
346> [!TIP]
347> This is a tip.
348
349> [!IMPORTANT]
350> This is important.
351
352> [!WARNING]
353> This is a warning.
354
355> [!CAUTION] Caution!!!!!
356> This is a caution.
357```
358> [!NOTE]
359> This is a note.
360
361> [!TIP]
362> This is a tip.
363
364> [!IMPORTANT]
365> This is important.
366
367> [!WARNING]
368> This is a warning.
369
370> [!CAUTION] Caution!!!!!
371> This is a caution.
372
373## Tables
374A table is written with `|`'s between columns and a row after the header row whose cell's contents are `-`'s.
375```md
376| Style | Syntax | Example |
377|---------------|---------------------|-------------------|
378| emphasis | `*emphasis*` | *emphasis* |
379| strong | `**strong**` | **strong** |
380```
381> | Style | Syntax | Example |
382> |---------------|---------------------|-------------------|
383> | emphasis | `*emphasis*` | *emphasis* |
384> | strong | `**strong**` | **strong** |
385
386## Code
387A code block is wrapped by two lines containing three backticks. A language can be added after the opening backticks.
388```md
389 ```rust
390 // some code
391 fn main() { println!("Hello, world!"); }
392 ```
393```
394> ```rust
395> // some code
396> fn main() { println!("Hello, world!"); }
397> ```
398
399You can also create a code block by indenting each line with four spaces. Indented code blocks cannot have a language.
400```md
401 // some code
402 fn main() { println!("Hello, world!"); }
403```
404> // some code
405> fn main() { println!("Hello, world!"); }
406
407## Thematic Breaks
408A thematic break is written with `***`, `---`, or `___` and shows a horizontal line across the page.
409```md
410***
411---
412___
413```
414> ***
415> ---
416> ___
417"#;
418}