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