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, Lb};
12use libsecp256k1::SecretKey;
13use qrcode_generator::QrCodeEcc;
14
15use crate::io::network::ApiError;
16
17impl Lb {
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.as_ref().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.replace(account.clone())?;
53 db.base_metadata.insert(root_id, root)?;
54 db.last_synced.replace(last_synced as i64)?;
55 db.root.replace(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.replace(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.replace(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 fn export_account_private_key(&self) -> LbResult<String> {
211 let account = self.get_account()?;
212 Ok(base64::encode(account.private_key.serialize()))
213 }
214
215 pub fn export_account_phrase(&self) -> LbResult<String> {
216 let account = self.get_account()?;
217 Ok(account.get_phrase()?.join(" "))
218 }
219
220 pub fn export_account_qr(&self) -> LbResult<Vec<u8>> {
221 let acct_secret = self.export_account_private_key()?;
222 qrcode_generator::to_png_to_vec(acct_secret, QrCodeEcc::Low, 1024)
223 .map_err(|err| core_err_unexpected(err).into())
224 }
225
226 #[instrument(level = "debug", skip(self), err(Debug))]
227 pub async fn delete_account(&self) -> LbResult<()> {
228 let account = self.get_account()?;
229
230 self.client
231 .request(account, DeleteAccountRequest {})
232 .await
233 .map_err(|err| match err {
234 ApiError::SendFailed(_) => LbErrKind::ServerUnreachable,
235 ApiError::ClientUpdateRequired => LbErrKind::ClientUpdateRequired,
236 _ => core_err_unexpected(err),
237 })?;
238
239 let mut tx = self.begin_tx().await;
240 let db = tx.db();
241
242 db.account.take()?;
243 db.last_synced.take()?;
244 db.base_metadata.clear()?;
245 db.root.take()?;
246 db.local_metadata.clear()?;
247 db.pub_key_lookup.clear()?;
248
249 Ok(())
252 }
253
254 const WELCOME_MESSAGE: &'static str = r#"# Markdown Syntax
255Markdown is a language for easily formatting your documents. This document can help you get started.
256
257## Styled Text
258To style text, wrap your text in the corresponding characters.
259| Style | Syntax | Example |
260|---------------|---------------------|-------------------|
261| emphasis | `*emphasis*` | *emphasis* |
262| strong | `**strong**` | **strong** |
263| strikethrough | `~~strikethrough~~` | ~~strikethrough~~ |
264| underline | `__underline__` | __underline__ |
265| code | ``code`` | `code` |
266| highlight | `==highlight==` | ==highlight== |
267| spoiler | `||spoiler||` | ||spoiler|| |
268| superscript | `^superscript^` | ^superscript^ |
269| subscript | `~subscript~` | ~subscript~ |
270
271## Links
272To 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.
273```md
274[Lockbook's website](https://lockbook.net)
275```
276> [Lockbook's website](https://lockbook.net)
277
278## Images
279To embed an image, add a `!` to the beginning of the link syntax.
280```md
281
282```
283> 
284
285## Headings
286To create a heading, add up to six `#`'s plus a space before your text. More `#`'s create a smaller heading.
287```md
288# Heading 1
289## Heading 2
290### Heading 3
291#### Heading 4
292##### Heading 5
293###### Heading 6
294```
295> # Heading 1
296> ## Heading 2
297> ### Heading 3
298> #### Heading 4
299> ##### Heading 5
300> ###### Heading 6
301
302## Lists
303Create 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*.
304```md
305* bulleted list item
306- bulleted list item
307+ bulleted list item
308
3091. numbered list item
3101. numbered list item
3111. numbered list item
312
313- [ ] task list item
314- [x] task list item
315```
316>* bulleted list item
317>- bulleted list item
318>+ bulleted list item
319>
320>1. numbered list item
321>1. numbered list item
322>1. numbered list item
323>
324>- [ ] task list item
325>- [x] task list item
326
327List 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).
328```md
329* This is a bulleted list
330 * An inner item needs at least 2 spaces
3311. This is a numbered list
332 1. An inner item needs at least 3 spaces
333* [ ] This is a task list
334 * [ ] An inner item needs at least 2 spaces
335```
336> * This is a bulleted list
337> * An inner item needs at least 2 spaces
338> 1. This is a numbered list
339> 1. An inner item needs at least 3 spaces
340> * [ ] This is a task list
341> * [ ] An inner item needs at least 2 spaces
342
343List 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.
344```md
345* This item contains text,
346 > a quote
347 ### and a heading.
348* This item contains two lines of text.
349The second line doesn't need spaces.
350```
351> * This item contains text,
352> > a quote
353> ### and a heading.
354> * This item contains two lines of text.
355> The second line doesn't need spaces.
356
357## Quotes
358To create a block quote, add `> ` to each line.
359```md
360> This is a quote
361```
362> This is a quote
363
364Like list items, block quotes can contain formatted content.
365```md
366> This quote contains some text,
367> ```rust
368> // some code
369> fn main() { println!("Hello, world!"); }
370> ```
371> ### and a heading.
372
373> This quote contains two lines of text.
374The second line doesn't need added characters.
375```
376> This quote contains some text,
377> ```rust
378> // some code
379> fn main() { println!("Hello, world!"); }
380> ```
381> ### and a heading.
382
383> This quote contains two lines of text.
384The second line doesn't need added characters.
385
386## Alerts
387To 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.
388```md
389> [!NOTE]
390> This is a note.
391
392> [!TIP]
393> This is a tip.
394
395> [!IMPORTANT]
396> This is important.
397
398> [!WARNING]
399> This is a warning.
400
401> [!CAUTION] Caution!!!!!
402> This is a caution.
403```
404> [!NOTE]
405> This is a note.
406
407> [!TIP]
408> This is a tip.
409
410> [!IMPORTANT]
411> This is important.
412
413> [!WARNING]
414> This is a warning.
415
416> [!CAUTION] Caution!!!!!
417> This is a caution.
418
419## Tables
420A table is written with `|`'s between columns and a row after the header row whose cell's contents are `-`'s.
421```md
422| Style | Syntax | Example |
423|---------------|---------------------|-------------------|
424| emphasis | `*emphasis*` | *emphasis* |
425| strong | `**strong**` | **strong** |
426```
427> | Style | Syntax | Example |
428> |---------------|---------------------|-------------------|
429> | emphasis | `*emphasis*` | *emphasis* |
430> | strong | `**strong**` | **strong** |
431
432## Code
433A code block is wrapped by two lines containing three backticks. A language can be added after the opening backticks.
434```md
435 ```rust
436 // some code
437 fn main() { println!("Hello, world!"); }
438 ```
439```
440> ```rust
441> // some code
442> fn main() { println!("Hello, world!"); }
443> ```
444
445You can also create a code block by indenting each line with four spaces. Indented code blocks cannot have a language.
446```md
447 // some code
448 fn main() { println!("Hello, world!"); }
449```
450> // some code
451> fn main() { println!("Hello, world!"); }
452
453## Thematic Breaks
454A thematic break is written with `***`, `---`, or `___` and shows a horizontal line across the page.
455```md
456***
457---
458___
459```
460> ***
461> ---
462> ___
463"#;
464}