1use mongodb::bson::{self, doc, document::Document, Bson, DateTime};
2pub use netsblox_api_common as api;
3use netsblox_api_common::{
4 oauth, ClientState, LibraryMetadata, NewUser, PublishState, RoleId, UserRole,
5};
6use netsblox_api_common::{
7 FriendInvite, FriendLinkState, GroupId, InvitationState, LinkedAccount, ProjectId, RoleData,
8 SaveState, ServiceHost, ServiceHostScope,
9};
10use serde::{Deserialize, Serialize};
11use sha2::{Digest, Sha512};
12use std::{
13 collections::HashMap,
14 time::{Duration, SystemTime},
15};
16use uuid::Uuid;
17
18#[derive(Serialize, Deserialize, Clone, Debug)]
19#[serde(rename_all = "camelCase")]
20pub struct User {
21 pub username: String,
22 pub email: String,
23 pub hash: String,
24 pub salt: Option<String>,
25 pub group_id: Option<GroupId>,
26 pub role: UserRole,
27 pub created_at: DateTime,
28 pub linked_accounts: Vec<LinkedAccount>,
29 pub services_hosts: Option<Vec<ServiceHost>>,
30 pub service_settings: HashMap<String, String>,
31}
32
33impl User {
34 pub fn is_member(&self) -> bool {
35 self.group_id.is_some()
36 }
37}
38
39impl From<User> for Bson {
40 fn from(user: User) -> Bson {
41 Bson::Document(doc! {
42 "username": user.username,
43 "email": user.email,
44 "hash": user.hash,
45 "salt": user.salt,
46 "groupId": user.group_id,
47 "role": user.role,
48 "createdAt": user.created_at,
49 "linkedAccounts": user.linked_accounts,
50 "servicesHosts": user.services_hosts,
51 "serviceSettings": bson::to_bson(&user.service_settings).unwrap(),
52 })
53 }
54}
55
56impl From<User> for netsblox_api_common::User {
57 fn from(user: User) -> netsblox_api_common::User {
58 netsblox_api_common::User {
59 username: user.username,
60 email: user.email,
61 group_id: user.group_id,
62 role: user.role,
63 created_at: user.created_at.to_system_time(),
64 linked_accounts: user.linked_accounts,
65 services_hosts: user.services_hosts,
66 }
67 }
68}
69
70impl From<NewUser> for User {
71 fn from(user_data: NewUser) -> Self {
72 let salt = passwords::PasswordGenerator::new()
73 .length(8)
74 .exclude_similar_characters(true)
75 .numbers(true)
76 .spaces(false)
77 .generate_one()
78 .unwrap_or_else(|_err| "salt".to_owned());
79
80 let hash: String = if let Some(pwd) = user_data.password {
81 sha512(&(pwd + &salt))
82 } else {
83 "None".to_owned()
84 };
85
86 User {
87 username: user_data.username,
88 hash,
89 salt: Some(salt),
90 email: user_data.email,
91 group_id: user_data.group_id,
92 created_at: DateTime::from_system_time(SystemTime::now()),
93 linked_accounts: std::vec::Vec::new(),
94 role: user_data.role.unwrap_or(UserRole::User),
95 services_hosts: None,
96 service_settings: HashMap::new(),
97 }
98 }
99}
100
101#[derive(Serialize, Deserialize, Clone)]
102#[serde(rename_all = "camelCase")]
103pub struct BannedAccount {
104 pub username: String,
105 pub email: String,
106 pub banned_at: DateTime,
107}
108
109impl BannedAccount {
110 pub fn new(username: String, email: String) -> BannedAccount {
111 let banned_at = DateTime::now();
112 BannedAccount {
113 username,
114 email,
115 banned_at,
116 }
117 }
118}
119
120impl From<BannedAccount> for Bson {
121 fn from(account: BannedAccount) -> Self {
122 Bson::Document(doc! {
123 "username": account.username,
124 "email": account.email,
125 "bannedAt": account.banned_at,
126 })
127 }
128}
129
130impl From<BannedAccount> for api::BannedAccount {
131 fn from(account: BannedAccount) -> Self {
132 api::BannedAccount {
133 username: account.username,
134 email: account.email,
135 banned_at: account.banned_at.into(),
136 }
137 }
138}
139
140#[derive(Serialize, Deserialize, Clone, Debug)]
141#[serde(rename_all = "camelCase")]
142pub struct Group {
143 pub id: GroupId,
144 pub owner: String,
145 pub name: String,
146 pub services_hosts: Option<Vec<ServiceHost>>,
147 pub service_settings: HashMap<String, String>,
148}
149
150impl Group {
151 pub fn new(owner: String, name: String) -> Self {
152 Self {
153 id: api::GroupId::new(Uuid::new_v4().to_string()),
154 name,
155 owner,
156 service_settings: HashMap::new(),
157 services_hosts: None,
158 }
159 }
160
161 pub fn from_data(owner: String, data: api::CreateGroupData) -> Self {
162 Self {
163 id: api::GroupId::new(Uuid::new_v4().to_string()),
164 owner,
165 name: data.name,
166 service_settings: HashMap::new(),
167 services_hosts: data.services_hosts,
168 }
169 }
170}
171
172impl From<Group> for netsblox_api_common::Group {
173 fn from(group: Group) -> netsblox_api_common::Group {
174 netsblox_api_common::Group {
175 id: group.id,
176 owner: group.owner,
177 name: group.name,
178 services_hosts: group.services_hosts,
179 }
180 }
181}
182
183impl From<Group> for Bson {
184 fn from(group: Group) -> Self {
185 let mut settings = Document::new();
186 group.service_settings.into_iter().for_each(|(k, v)| {
187 settings.insert(k, v);
188 });
189
190 Bson::Document(doc! {
191 "id": group.id,
192 "owner": group.owner,
193 "name": group.name,
194 "serviceSettings": settings,
195 "servicesHosts": group.services_hosts,
196 })
197 }
198}
199
200#[derive(Serialize, Deserialize, Clone, Debug)]
201#[serde(rename_all = "camelCase")]
202pub struct CollaborationInvite {
203 pub id: String,
204 pub sender: String,
205 pub receiver: String,
206 pub project_id: ProjectId,
207 pub state: InvitationState,
208 pub created_at: DateTime,
209}
210
211impl CollaborationInvite {
212 pub fn new(sender: String, receiver: String, project_id: ProjectId) -> Self {
213 CollaborationInvite {
214 id: Uuid::new_v4().to_string(),
215 sender,
216 receiver,
217 project_id,
218 state: InvitationState::Pending,
219 created_at: DateTime::from_system_time(SystemTime::now()),
220 }
221 }
222}
223
224impl From<CollaborationInvite> for Bson {
225 fn from(invite: CollaborationInvite) -> Self {
226 Bson::Document(doc! {
227 "id": invite.id,
228 "sender": invite.sender,
229 "receiver": invite.receiver,
230 "projectId": invite.project_id,
231 "state": invite.state,
232 "createdAt": invite.created_at,
233 })
234 }
235}
236
237impl From<CollaborationInvite> for netsblox_api_common::CollaborationInvite {
238 fn from(user: CollaborationInvite) -> netsblox_api_common::CollaborationInvite {
239 netsblox_api_common::CollaborationInvite {
240 id: user.id,
241 sender: user.sender,
242 receiver: user.receiver,
243 project_id: user.project_id,
244 state: user.state,
245 created_at: user.created_at.to_system_time(),
246 }
247 }
248}
249
250#[derive(Deserialize, Serialize, Clone, Debug)]
251#[serde(rename_all = "camelCase")]
252pub struct FriendLink {
253 pub id: api::FriendLinkId,
254 pub sender: String,
255 pub recipient: String,
256 pub state: FriendLinkState,
257 pub created_at: DateTime,
258 pub updated_at: DateTime,
259}
260
261impl FriendLink {
262 pub fn new(sender: String, recipient: String, state: Option<FriendLinkState>) -> FriendLink {
263 let created_at = DateTime::from_system_time(SystemTime::now());
264 FriendLink {
265 id: Uuid::new_v4().to_string(),
266 sender,
267 recipient,
268 state: state.unwrap_or(FriendLinkState::Pending),
269 created_at,
270 updated_at: created_at,
271 }
272 }
273}
274
275impl From<FriendLink> for api::FriendLink {
276 fn from(link: FriendLink) -> api::FriendLink {
277 api::FriendLink {
278 id: link.id,
279 sender: link.sender,
280 recipient: link.recipient,
281 state: link.state,
282 created_at: link.created_at.into(),
283 updated_at: link.updated_at.into(),
284 }
285 }
286}
287
288impl From<FriendLink> for FriendInvite {
289 fn from(link: FriendLink) -> FriendInvite {
290 FriendInvite {
291 id: link.id,
292 sender: link.sender,
293 recipient: link.recipient,
294 created_at: link.created_at.to_system_time(),
295 }
296 }
297}
298
299impl From<FriendLink> for Bson {
300 fn from(link: FriendLink) -> Bson {
301 Bson::Document(doc! {
302 "id": link.id,
303 "sender": link.sender,
304 "recipient": link.recipient,
305 "state": link.state,
306 "createdAt": link.created_at,
307 "updatedAt": link.updated_at,
308 })
309 }
310}
311
312#[derive(Deserialize, Serialize, Clone, Debug)]
313#[serde(rename_all = "camelCase")]
314pub struct NetworkTraceMetadata {
315 pub id: String,
316 pub start_time: DateTime,
317 pub end_time: Option<DateTime>,
318}
319
320impl NetworkTraceMetadata {
321 pub fn new() -> Self {
322 Self {
323 id: Uuid::new_v4().to_string(),
324 start_time: DateTime::now(),
325 end_time: None,
326 }
327 }
328}
329
330impl From<NetworkTraceMetadata> for Bson {
331 fn from(link: NetworkTraceMetadata) -> Bson {
332 Bson::Document(doc! {
333 "id": link.id,
334 "startTime": link.start_time,
335 "endTime": link.end_time,
336 })
337 }
338}
339
340impl From<NetworkTraceMetadata> for netsblox_api_common::NetworkTraceMetadata {
341 fn from(trace: NetworkTraceMetadata) -> netsblox_api_common::NetworkTraceMetadata {
342 netsblox_api_common::NetworkTraceMetadata {
343 id: trace.id,
344 start_time: trace.start_time.into(),
345 end_time: trace.end_time.map(|t| t.into()),
346 }
347 }
348}
349
350#[derive(Deserialize, Serialize, Clone, Debug)]
351#[serde(rename_all = "camelCase")]
352pub struct ProjectMetadata {
353 pub id: ProjectId,
354 pub owner: String,
355 pub name: String,
356 pub updated: DateTime,
357 pub state: PublishState,
358 pub collaborators: std::vec::Vec<String>,
359 pub origin_time: DateTime,
360 pub save_state: SaveState,
361 pub delete_at: Option<DateTime>,
362 pub network_traces: Vec<NetworkTraceMetadata>,
363 pub roles: HashMap<RoleId, RoleMetadata>,
364}
365
366impl ProjectMetadata {
367 pub fn new(
368 owner: &str,
369 name: &str,
370 roles: HashMap<RoleId, RoleMetadata>,
371 save_state: SaveState,
372 ) -> ProjectMetadata {
373 let origin_time = DateTime::now();
374
375 let delete_at = match save_state {
376 SaveState::Saved => None,
377 _ => {
378 let ten_minutes = Duration::new(10 * 60, 0);
380 let ten_mins_from_now = SystemTime::now().checked_add(ten_minutes).unwrap();
381 Some(DateTime::from_system_time(ten_mins_from_now))
382 }
383 };
384
385 ProjectMetadata {
386 id: ProjectId::new(Uuid::new_v4().to_string()),
387 owner: owner.to_owned(),
388 name: name.to_owned(),
389 updated: origin_time,
390 origin_time,
391 state: PublishState::Private,
392 collaborators: vec![],
393 save_state,
394 delete_at,
395 network_traces: Vec::new(),
396 roles,
397 }
398 }
399}
400
401impl From<ProjectMetadata> for Bson {
402 fn from(metadata: ProjectMetadata) -> Bson {
403 let mut roles = Document::new();
404 metadata.roles.into_iter().for_each(|(id, md)| {
405 roles.insert(id.as_str(), md);
406 });
407
408 Bson::Document(doc! {
409 "id": metadata.id,
410 "owner": metadata.owner,
411 "name": metadata.name,
412 "updated": metadata.updated,
413 "originTime": metadata.origin_time,
414 "state": metadata.state,
415 "collaborators": metadata.collaborators,
416 "saveState": metadata.save_state,
417 "roles": roles,
418 "deleteAt": metadata.delete_at,
419 "networkTraces": metadata.network_traces,
420 })
421 }
422}
423
424impl From<ProjectMetadata> for netsblox_api_common::ProjectMetadata {
425 fn from(metadata: ProjectMetadata) -> netsblox_api_common::ProjectMetadata {
426 netsblox_api_common::ProjectMetadata {
427 id: metadata.id,
428 owner: metadata.owner,
429 name: metadata.name,
430 origin_time: metadata.origin_time.to_system_time(),
431 updated: metadata.updated.to_system_time(),
432 state: metadata.state,
433 collaborators: metadata.collaborators,
434 save_state: metadata.save_state,
435 network_traces: metadata
436 .network_traces
437 .into_iter()
438 .map(|t| t.into())
439 .collect(),
440 roles: metadata
441 .roles
442 .into_iter()
443 .map(|(k, v)| (k, v.into()))
444 .collect(),
445 }
446 }
447}
448
449#[derive(Deserialize, Serialize, Clone, Debug)]
450#[serde(rename_all = "camelCase")]
451pub struct Project {
452 pub id: ProjectId,
453 pub owner: String,
454 pub name: String,
455 pub updated: DateTime,
456 pub state: PublishState,
457 pub collaborators: std::vec::Vec<String>,
458 pub origin_time: DateTime,
459 pub save_state: SaveState,
460 pub roles: HashMap<RoleId, RoleData>,
461}
462
463impl From<Project> for netsblox_api_common::Project {
464 fn from(project: Project) -> netsblox_api_common::Project {
465 netsblox_api_common::Project {
466 id: project.id,
467 owner: project.owner,
468 name: project.name,
469 origin_time: project.origin_time.to_system_time(),
470 updated: project.updated.to_system_time(),
471 state: project.state,
472 collaborators: project.collaborators,
473 save_state: project.save_state,
474 roles: project.roles,
475 }
476 }
477}
478
479#[derive(Deserialize, Serialize, Clone, Debug)]
480#[serde(rename_all = "camelCase")]
481pub struct RoleMetadata {
482 pub name: String,
483 pub code: String,
484 pub media: String,
485 pub updated: DateTime,
486}
487
488impl From<RoleMetadata> for netsblox_api_common::RoleMetadata {
489 fn from(metadata: RoleMetadata) -> netsblox_api_common::RoleMetadata {
490 netsblox_api_common::RoleMetadata {
491 name: metadata.name,
492 code: metadata.code,
493 media: metadata.media,
494 }
495 }
496}
497
498impl From<RoleMetadata> for Bson {
499 fn from(metadata: RoleMetadata) -> Bson {
500 Bson::Document(doc! {
501 "name": metadata.name,
502 "code": metadata.code,
503 "media": metadata.media,
504 "updated": metadata.updated,
505 })
506 }
507}
508
509#[derive(Deserialize, Serialize, Debug, Clone)]
510#[serde(rename_all = "camelCase")]
511pub struct OccupantInvite {
512 pub username: String,
513 pub project_id: ProjectId,
514 pub role_id: RoleId,
515 created_at: DateTime,
516}
517
518impl OccupantInvite {
519 pub fn new(target: String, project_id: ProjectId, role_id: RoleId) -> Self {
520 OccupantInvite {
521 project_id,
522 username: target,
523 role_id,
524 created_at: DateTime::from_system_time(SystemTime::now()),
525 }
526 }
527}
528
529impl From<OccupantInvite> for api::OccupantInvite {
530 fn from(invite: OccupantInvite) -> api::OccupantInvite {
531 api::OccupantInvite {
532 username: invite.username,
533 project_id: invite.project_id,
534 role_id: invite.role_id,
535 created_at: invite.created_at.into(),
536 }
537 }
538}
539
540#[derive(Deserialize, Serialize, Debug, Clone)]
541#[serde(rename_all = "camelCase")]
542pub struct SentMessage {
543 pub project_id: ProjectId,
544 pub recipients: Vec<ClientState>,
545 pub time: DateTime,
546 pub source: ClientState,
547
548 pub content: serde_json::Value,
549}
550
551#[derive(Deserialize, Serialize, Debug, Clone)]
553#[serde(rename_all = "camelCase")]
554pub struct LogMessage {
555 pub sender: String,
556 pub recipients: Vec<String>,
557 pub content: serde_json::Value,
558 pub created_at: DateTime,
559}
560
561impl From<api::LogMessage> for LogMessage {
563 fn from(value: api::LogMessage) -> Self {
564 LogMessage {
565 sender: value.sender,
566 recipients: value.recipients,
567 content: value.content,
568 created_at: DateTime::now(),
569 }
570 }
571}
572
573impl From<LogMessage> for api::LogMessage {
574 fn from(value: LogMessage) -> Self {
575 api::LogMessage {
576 sender: value.sender,
577 recipients: value.recipients,
578 content: value.content,
579 }
580 }
581}
582
583impl SentMessage {
584 pub fn new(
585 project_id: ProjectId,
586 source: ClientState,
587 recipients: Vec<ClientState>,
588 content: serde_json::Value,
589 ) -> Self {
590 let time = DateTime::now();
591 SentMessage {
592 project_id,
593 recipients,
594 time,
595 source,
596 content,
597 }
598 }
599}
600
601impl From<SentMessage> for api::SentMessage {
602 fn from(msg: SentMessage) -> api::SentMessage {
603 api::SentMessage {
604 project_id: msg.project_id,
605 recipients: msg.recipients,
606 time: msg.time.into(),
607 source: msg.source,
608 content: msg.content,
609 }
610 }
611}
612
613#[derive(Deserialize, Serialize, Debug, Clone)]
614#[serde(rename_all = "camelCase")]
615pub struct SetPasswordToken {
616 pub username: String,
617 pub secret: String,
618 pub created_at: DateTime,
619}
620
621impl SetPasswordToken {
622 pub fn new(username: String) -> Self {
623 let secret = Uuid::new_v4().to_string();
624 let created_at = DateTime::from_system_time(SystemTime::now());
625
626 SetPasswordToken {
627 username,
628 secret,
629 created_at,
630 }
631 }
632}
633
634impl From<SetPasswordToken> for Bson {
635 fn from(token: SetPasswordToken) -> Bson {
636 Bson::Document(doc! {
637 "username": token.username,
638 "secret": token.secret,
639 "createdAt": token.created_at,
640 })
641 }
642}
643
644#[derive(Deserialize, Serialize, Debug, Clone)]
645#[serde(rename_all = "camelCase")]
646pub struct AuthorizedServiceHost {
647 pub url: String,
648 pub id: String,
649 pub visibility: ServiceHostScope,
650 pub secret: String,
651}
652
653impl AuthorizedServiceHost {
654 pub fn new(url: String, id: String, visibility: ServiceHostScope) -> Self {
655 let secret = Uuid::new_v4().to_string();
656 AuthorizedServiceHost {
657 url,
658 id,
659 secret,
660 visibility,
661 }
662 }
663
664 pub fn auth_header(&self) -> (&'static str, String) {
665 let token = self.id.clone() + ":" + &self.secret;
666 ("X-Authorization", token)
667 }
668}
669
670impl From<AuthorizedServiceHost> for Bson {
671 fn from(host: AuthorizedServiceHost) -> Bson {
672 Bson::Document(doc! {
673 "url": host.url,
674 "id": host.id,
675 "visibility": host.visibility,
676 "secret": host.secret,
677 })
678 }
679}
680
681impl From<netsblox_api_common::AuthorizedServiceHost> for AuthorizedServiceHost {
682 fn from(data: netsblox_api_common::AuthorizedServiceHost) -> AuthorizedServiceHost {
683 AuthorizedServiceHost::new(data.url, data.id, data.visibility)
684 }
685}
686
687impl From<AuthorizedServiceHost> for netsblox_api_common::AuthorizedServiceHost {
688 fn from(host: AuthorizedServiceHost) -> netsblox_api_common::AuthorizedServiceHost {
689 netsblox_api_common::AuthorizedServiceHost {
690 id: host.id,
691 url: host.url,
692 visibility: host.visibility,
693 }
694 }
695}
696
697impl From<AuthorizedServiceHost> for netsblox_api_common::ServiceHost {
698 fn from(host: AuthorizedServiceHost) -> netsblox_api_common::ServiceHost {
699 let categories = match host.visibility {
700 ServiceHostScope::Public(cats) => cats,
701 ServiceHostScope::Private => Vec::new(),
702 };
703
704 netsblox_api_common::ServiceHost {
705 url: host.url,
706 categories,
707 }
708 }
709}
710
711#[derive(Serialize, Deserialize, Clone)]
712#[serde(rename_all = "camelCase")]
713pub struct Library {
714 pub owner: String,
715 pub name: String,
716 pub notes: String,
717 pub blocks: String,
718 pub state: PublishState,
719}
720
721impl From<Library> for LibraryMetadata {
722 fn from(library: Library) -> LibraryMetadata {
723 LibraryMetadata::new(
724 library.owner.clone(),
725 library.name.clone(),
726 library.state,
727 Some(library.notes),
728 )
729 }
730}
731
732impl From<Library> for Bson {
733 fn from(library: Library) -> Self {
734 Bson::Document(doc! {
735 "owner": library.owner,
736 "name": library.name,
737 "notes": library.notes,
738 "blocks": library.blocks,
739 "state": library.state,
740 })
741 }
742}
743
744#[derive(Serialize, Deserialize)]
745#[serde(rename_all = "camelCase")]
746pub struct OAuthClient {
747 pub id: oauth::ClientId,
748 pub name: String,
749 created_at: DateTime,
750 hash: String,
751 salt: String,
752}
753
754impl OAuthClient {
755 pub fn new(name: String, password: String) -> Self {
756 let salt = passwords::PasswordGenerator::new()
757 .length(8)
758 .exclude_similar_characters(true)
759 .numbers(true)
760 .spaces(false)
761 .generate_one()
762 .unwrap_or_else(|_err| "salt".to_owned());
763
764 let hash = sha512(&(password + &salt));
765 Self {
766 id: oauth::ClientId::new(Uuid::new_v4().to_string()),
767 name,
768 created_at: DateTime::from_system_time(SystemTime::now()),
769 hash,
770 salt,
771 }
772 }
773}
774
775impl From<OAuthClient> for Bson {
776 fn from(client: OAuthClient) -> Bson {
777 Bson::Document(doc! {
778 "id": client.id,
779 "name": client.name,
780 "createdAt": client.created_at,
781 "hash": client.hash,
782 "salt": client.salt,
783 })
784 }
785}
786
787impl From<OAuthClient> for oauth::Client {
788 fn from(client: OAuthClient) -> oauth::Client {
789 oauth::Client {
790 id: client.id,
791 name: client.name,
792 }
793 }
794}
795
796#[derive(Serialize, Deserialize)]
797#[serde(rename_all = "camelCase")]
798pub struct OAuthToken {
799 pub id: oauth::TokenId,
800 pub client_id: oauth::ClientId,
801 pub username: String,
802 pub created_at: DateTime,
803}
804
805impl OAuthToken {
806 pub fn new(client_id: oauth::ClientId, username: String) -> Self {
807 let id = oauth::TokenId::new(Uuid::new_v4().to_string());
808 let created_at = DateTime::from_system_time(SystemTime::now());
809
810 Self {
811 id,
812 client_id,
813 username,
814 created_at,
815 }
816 }
817}
818
819impl From<OAuthToken> for oauth::Token {
820 fn from(token: OAuthToken) -> oauth::Token {
821 oauth::Token {
822 id: token.id,
823 client_id: token.client_id,
824 username: token.username,
825 created_at: token.created_at.to_system_time(),
826 }
827 }
828}
829
830impl From<OAuthToken> for Bson {
831 fn from(token: OAuthToken) -> Bson {
832 Bson::Document(doc! {
833 "id": token.id,
834 "client_id": token.client_id,
835 "username": token.username,
836 "createdAt": token.created_at,
837 })
838 }
839}
840
841pub(crate) fn sha512(text: &str) -> String {
842 let mut hasher = Sha512::new();
843 hasher.update(text);
844 let hash = hasher.finalize();
845 hex::encode(hash)
846}
847
848#[derive(Serialize, Deserialize, Clone, Debug)]
852#[serde(rename_all = "camelCase")]
853pub struct MagicLink {
854 pub id: api::MagicLinkId,
855 pub email: String,
856 pub created_at: DateTime,
857}
858
859impl MagicLink {
860 pub fn new(email: String) -> Self {
861 Self {
862 id: api::MagicLinkId::new(Uuid::new_v4().to_string()),
863 email,
864 created_at: DateTime::now(),
865 }
866 }
867}
868
869impl From<MagicLink> for Bson {
870 fn from(link: MagicLink) -> Bson {
871 Bson::Document(doc! {
872 "id": link.id,
873 "email": link.email,
874 "createdAt": link.created_at,
875 })
876 }
877}
878
879#[cfg(test)]
880mod tests {
881 use super::*;
882
883 #[test]
884 fn test_dont_schedule_deletion_for_saved_projects() {
885 let metadata =
886 ProjectMetadata::new("owner", "someProject", HashMap::new(), SaveState::Saved);
887 assert!(metadata.delete_at.is_none());
888 }
889
890 #[test]
891 fn test_schedule_deletion_for_created_projects() {
892 let metadata =
894 ProjectMetadata::new("owner", "someProject", HashMap::new(), SaveState::Created);
895 assert!(metadata.delete_at.is_some());
896 }
897
898 #[test]
899 fn test_pub_auth_host_to_host_preserves_cats() {
900 let categories = vec!["cat1".into()];
901 let auth_host = AuthorizedServiceHost {
902 url: "http://localhost:8000".into(),
903 id: "SomeTrustedHost".into(),
904 secret: "SomeSecret".into(),
905 visibility: ServiceHostScope::Public(categories.clone()),
906 };
907 let host: ServiceHost = auth_host.into();
908
909 assert_eq!(host.categories.len(), 1);
910 assert_eq!(&host.categories.into_iter().next().unwrap(), "cat1");
911 }
912
913 #[test]
914 fn test_priv_auth_host_to_host_no_cats() {
915 let auth_host = AuthorizedServiceHost {
916 url: "http://localhost:8000".into(),
917 id: "SomeTrustedHost".into(),
918 secret: "SomeSecret".into(),
919 visibility: ServiceHostScope::Private,
920 };
921 let host: ServiceHost = auth_host.into();
922 assert_eq!(host.categories.len(), 0);
923 }
924}