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 WelcomeDoc::FramgentedArchetypes => {
76 let samples = self
77 .create_file("samples", &root_id, FileType::Folder)
78 .await?;
79
80 let todo = self
81 .create_file("todo.md", &samples.id, FileType::Document)
82 .await?;
83 self.write_document(todo.id, include_bytes!("samples/todo.md"))
84 .await?;
85
86 let journal = self
87 .create_file("journal.md", &samples.id, FileType::Document)
88 .await?;
89 let journal_content = include_str!("samples/journal.md")
90 .replace("{date}", &chrono::Local::now().format("%y-%m-%d").to_string());
91 self.write_document(journal.id, journal_content.as_bytes())
92 .await?;
93
94 let pdf = self
95 .create_file("bitcoin.pdf", &samples.id, FileType::Document)
96 .await?;
97 self.write_document(pdf.id, include_bytes!("samples/bitcoin.pdf"))
98 .await?;
99
100 let drawing = self
101 .create_file("drawing.svg", &samples.id, FileType::Document)
102 .await?;
103 self.write_document(drawing.id, include_bytes!("samples/drawing.svg"))
104 .await?;
105
106 let welcome = self
107 .create_file("welcome.md", &root_id, FileType::Document)
108 .await?;
109 self.write_document(welcome.id, include_bytes!("samples/welcome.md"))
110 .await?;
111
112 self.sync().await?;
113 }
114 WelcomeDoc::HypeWelcomeDoc => {
115 let welcome_doc = self
116 .create_file("welcome.md", &root_id, FileType::Document)
117 .await?;
118 self.write_document(welcome_doc.id, include_bytes!("samples/hype_welcome.md"))
119 .await?;
120 self.sync().await?;
121 }
122 };
123 }
124
125 self.events.meta_changed(Actor::User(None));
126 self.events.signed_in();
127
128 Ok(account)
129 }
130
131 #[instrument(level = "debug", skip(self, key), err(Debug))]
132 pub async fn import_account(&self, key: &str, api_url: Option<&str>) -> LbResult<Account> {
133 if self.get_account().is_ok() {
134 warn!("tried to import an account, but account exists already.");
135 return Err(LbErrKind::AccountExists.into());
136 }
137
138 if let Ok(key) = base64::decode(key) {
139 if let Ok(account) = bincode::deserialize(&key[..]) {
140 return self.import_account_private_key_v1(account).await;
141 } else if let Ok(key) = SecretKey::parse_slice(&key) {
142 return self
143 .import_account_private_key_v2(key, api_url.unwrap_or(DEFAULT_API_LOCATION))
144 .await;
145 }
146 }
147
148 let phrase: [&str; 24] = key
149 .split([' ', ','])
150 .filter(|maybe_word| !maybe_word.is_empty())
151 .collect::<Vec<_>>()
152 .try_into()
153 .map_err(|_| LbErrKind::AccountStringCorrupted)?;
154
155 self.import_account_phrase(phrase, api_url.unwrap_or(DEFAULT_API_LOCATION))
156 .await
157 }
158
159 pub async fn import_account_private_key_v1(&self, account: Account) -> LbResult<Account> {
160 let server_public_key = self
161 .client
162 .request(&account, GetPublicKeyRequest { username: account.username.clone() })
163 .await?
164 .key;
165
166 let account_public_key = account.public_key();
167
168 if account_public_key != server_public_key {
169 return Err(LbErrKind::UsernamePublicKeyMismatch.into());
170 }
171
172 let mut tx = self.begin_tx().await;
173 let db = tx.db();
174 db.account.insert(account.clone())?;
175 self.keychain.cache_account(account.clone()).await?;
176
177 Ok(account)
178 }
179
180 pub async fn import_account_private_key_v2(
181 &self, private_key: SecretKey, api_url: &str,
182 ) -> LbResult<Account> {
183 let mut account =
184 Account { username: "".to_string(), api_url: api_url.to_string(), private_key };
185 let public_key = account.public_key();
186
187 account.username = self
188 .client
189 .request(&account, GetUsernameRequest { key: public_key })
190 .await?
191 .username;
192
193 let mut tx = self.begin_tx().await;
194 let db = tx.db();
195 db.account.insert(account.clone())?;
196 self.keychain.cache_account(account.clone()).await?;
197
198 Ok(account)
199 }
200
201 pub async fn import_account_phrase(
202 &self, phrase: [&str; 24], api_url: &str,
203 ) -> LbResult<Account> {
204 let private_key = Account::phrase_to_private_key(phrase)?;
205 self.import_account_private_key_v2(private_key, api_url)
206 .await
207 }
208
209 #[instrument(level = "debug", skip(self), err(Debug))]
210 pub async fn delete_account(&self) -> LbResult<()> {
211 let account = self.get_account()?;
212
213 self.client
214 .request(account, DeleteAccountRequest {})
215 .await
216 .map_err(|err| match err {
217 ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
218 ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
219 _ => core_err_unexpected(err),
220 })?;
221
222 let mut tx = self.begin_tx().await;
223 let db = tx.db();
224
225 db.account.clear()?;
226 db.last_synced.clear()?;
227 db.base_metadata.clear()?;
228 db.root.clear()?;
229 db.local_metadata.clear()?;
230 db.pub_key_lookup.clear()?;
231
232 Ok(())
235 }
236
237 const WELCOME_MESSAGE: &'static str = r#"# Markdown Syntax
238Markdown is a language for easily formatting your documents. This document can help you get started.
239
240## Styled Text
241To style text, wrap your text in the corresponding characters.
242| Style | Syntax | Example |
243|---------------|---------------------|-------------------|
244| emphasis | `*emphasis*` | *emphasis* |
245| strong | `**strong**` | **strong** |
246| strikethrough | `~~strikethrough~~` | ~~strikethrough~~ |
247| underline | `__underline__` | __underline__ |
248| code | ``code`` | `code` |
249| highlight | `==highlight==` | ==highlight== |
250| spoiler | `||spoiler||` | ||spoiler|| |
251| superscript | `^superscript^` | ^superscript^ |
252| subscript | `~subscript~` | ~subscript~ |
253
254## Links
255To 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.
256```md
257[Lockbook's website](https://lockbook.net)
258```
259> [Lockbook's website](https://lockbook.net)
260
261## Images
262To embed an image, add a `!` to the beginning of the link syntax.
263```md
264
265```
266> 
267
268## Headings
269To create a heading, add up to six `#`'s plus a space before your text. More `#`'s create a smaller heading.
270```md
271# Heading 1
272## Heading 2
273### Heading 3
274#### Heading 4
275##### Heading 5
276###### Heading 6
277```
278> # Heading 1
279> ## Heading 2
280> ### Heading 3
281> #### Heading 4
282> ##### Heading 5
283> ###### Heading 6
284
285## Lists
286Create 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*.
287```md
288* bulleted list item
289- bulleted list item
290+ bulleted list item
291
2921. numbered list item
2931. numbered list item
2941. numbered list item
295
296- [ ] task list item
297- [x] task list item
298```
299>* bulleted list item
300>- bulleted list item
301>+ bulleted list item
302>
303>1. numbered list item
304>1. numbered list item
305>1. numbered list item
306>
307>- [ ] task list item
308>- [x] task list item
309
310List 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).
311```md
312* This is a bulleted list
313 * An inner item needs at least 2 spaces
3141. This is a numbered list
315 1. An inner item needs at least 3 spaces
316* [ ] This is a task list
317 * [ ] An inner item needs at least 2 spaces
318```
319> * This is a bulleted list
320> * An inner item needs at least 2 spaces
321> 1. This is a numbered list
322> 1. An inner item needs at least 3 spaces
323> * [ ] This is a task list
324> * [ ] An inner item needs at least 2 spaces
325
326List 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.
327```md
328* This item contains text,
329 > a quote
330 ### and a heading.
331* This item contains two lines of text.
332The second line doesn't need spaces.
333```
334> * This item contains text,
335> > a quote
336> ### and a heading.
337> * This item contains two lines of text.
338> The second line doesn't need spaces.
339
340## Quotes
341To create a block quote, add `> ` to each line.
342```md
343> This is a quote
344```
345> This is a quote
346
347Like list items, block quotes can contain formatted content.
348```md
349> This quote contains some text,
350> ```rust
351> // some code
352> fn main() { println!("Hello, world!"); }
353> ```
354> ### and a heading.
355
356> This quote contains two lines of text.
357The second line doesn't need added characters.
358```
359> This quote contains some text,
360> ```rust
361> // some code
362> fn main() { println!("Hello, world!"); }
363> ```
364> ### and a heading.
365
366> This quote contains two lines of text.
367The second line doesn't need added characters.
368
369## Alerts
370To 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.
371```md
372> [!NOTE]
373> This is a note.
374
375> [!TIP]
376> This is a tip.
377
378> [!IMPORTANT]
379> This is important.
380
381> [!WARNING]
382> This is a warning.
383
384> [!CAUTION] Caution!!!!!
385> This is a caution.
386```
387> [!NOTE]
388> This is a note.
389
390> [!TIP]
391> This is a tip.
392
393> [!IMPORTANT]
394> This is important.
395
396> [!WARNING]
397> This is a warning.
398
399> [!CAUTION] Caution!!!!!
400> This is a caution.
401
402## Tables
403A table is written with `|`'s between columns and a row after the header row whose cell's contents are `-`'s.
404```md
405| Style | Syntax | Example |
406|---------------|---------------------|-------------------|
407| emphasis | `*emphasis*` | *emphasis* |
408| strong | `**strong**` | **strong** |
409```
410> | Style | Syntax | Example |
411> |---------------|---------------------|-------------------|
412> | emphasis | `*emphasis*` | *emphasis* |
413> | strong | `**strong**` | **strong** |
414
415## Code
416A code block is wrapped by two lines containing three backticks. A language can be added after the opening backticks.
417```md
418 ```rust
419 // some code
420 fn main() { println!("Hello, world!"); }
421 ```
422```
423> ```rust
424> // some code
425> fn main() { println!("Hello, world!"); }
426> ```
427
428You can also create a code block by indenting each line with four spaces. Indented code blocks cannot have a language.
429```md
430 // some code
431 fn main() { println!("Hello, world!"); }
432```
433> // some code
434> fn main() { println!("Hello, world!"); }
435
436## Thematic Breaks
437A thematic break is written with `***`, `---`, or `___` and shows a horizontal line across the page.
438```md
439***
440---
441___
442```
443> ***
444> ---
445> ___
446"#;
447}
448
449impl crate::Lb {
450 #[instrument(level = "debug", skip(self), err(Debug))]
451 pub fn export_account_private_key(&self) -> LbResult<String> {
452 let account = self.get_account()?;
453 Ok(base64::encode(account.private_key.serialize()))
454 }
455
456 pub fn export_account_phrase(&self) -> LbResult<String> {
457 let account = self.get_account()?;
458 Ok(account.get_phrase()?.join(" "))
459 }
460
461 pub fn export_account_qr(&self) -> LbResult<Vec<u8>> {
462 let acct_secret = self.export_account_private_key()?;
463 qrcode_generator::to_png_to_vec(acct_secret, QrCodeEcc::Low, 1024)
464 .map_err(|err| core_err_unexpected(err).into())
465 }
466}