1use crate::naming::IDENTITY_AREA;
2use crate::package_routes::{DEFAULT_MOUNT, ListedRoute, PackageRoutes, ServedRoute, served_route};
3use crate::sources::{ConditionalWiring, module_tree};
4use crate::{Code, Identity, ModelError};
5
6pub(crate) const IDENTITY_CRATE: &str = "sofie_identity";
7const MEMBER_MOUNT: &str = DEFAULT_MOUNT;
9
10impl crate::AreaDefinition {
11 #[must_use]
13 pub fn is_member_area(&self) -> bool {
14 self.implements_member_area || self.needs_generated_member_area()
15 }
16}
17
18impl<'app> PackageRoutes<'app> {
19 pub fn identity(&self) -> Result<Vec<ServedRoute<'app>>, ModelError> {
22 let Some(identity) = &self.app.identity else {
23 return Ok(Vec::new());
24 };
25 let mut served = self.member_routes(identity)?;
26 let generic = identity
27 .mounts
28 .iter()
29 .filter(|mount| mount.mount != MEMBER_MOUNT);
30 served.extend(self.mounted(IDENTITY_CRATE, &identity.routes, generic));
31 Ok(served)
32 }
33
34 fn member_routes(
35 &self,
36 identity: &'app Identity,
37 ) -> Result<Vec<ServedRoute<'app>>, ModelError> {
38 let routes: Vec<&ListedRoute> = identity
39 .routes
40 .iter()
41 .filter(|route| route.mount == MEMBER_MOUNT)
42 .collect();
43 let mut served = Vec::new();
44 if routes.is_empty() {
45 return Ok(served);
46 }
47 for area in self.areas.iter().filter(|area| area.contract.is_some()) {
48 let Some(definition) = area
49 .definition
50 .filter(|definition| definition.is_member_area())
51 else {
52 continue;
53 };
54 let (Some(prefix), Some(paths)) =
55 (definition.prefix.as_deref(), definition.sign_in_paths()?)
56 else {
57 continue;
58 };
59 for route in &routes {
60 if route.path.contains("{sign_up}") && paths.sign_up.is_none() {
61 continue;
62 }
63 let path = route
64 .path
65 .replace("{sign_in}", &paths.sign_in)
66 .replace("{sign_up}", paths.sign_up.as_deref().unwrap_or_default())
67 .replace("{prefix}", prefix);
68 served.push(served_route(
69 IDENTITY_CRATE,
70 IDENTITY_AREA,
71 area.name,
72 path,
73 route,
74 ));
75 }
76 }
77 Ok(served)
78 }
79}
80
81impl Identity {
82 pub fn code(&self) -> Result<Code, ModelError> {
84 Ok(module_tree(&self.directory, ConditionalWiring::Allowed)?
85 .found
86 .code)
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use crate::testing::{self, TemporaryDirectory};
93
94 #[test]
95 fn signs_members_in_to_no_public_area_a_contract_defines() {
96 let directory = TemporaryDirectory::new("sofie-model-identity-areas");
97 let mut order = testing::module("order", directory.path("order"));
98 testing::define_area(&mut order, "member");
99 let mut docs = order.defined_areas[0].clone();
100 docs.name = "docs".to_owned();
101 docs.type_path[2] = "Docs".to_owned();
102 docs.prefix = Some("/docs".to_owned());
103 docs.principal = vec!["Visitor".to_owned()];
104 docs.implements_member_area = false;
105 order.defined_areas.push(docs);
106 directory.write(
107 "order/contract/src/area.rs",
108 "impl Area for Member {\n const SIGN_IN: &'static str = \"/sign-in\";\n}\n\
109 impl Area for Docs {\n const SIGN_IN: &'static str = \"/\";\n}\n",
110 );
111 let mut shop = testing::app(directory.root(), vec![order]);
112 shop.identity = Some(crate::Identity {
113 routes: vec![crate::ListedRoute {
114 mount: "routes".to_owned(),
115 method: "GET".to_owned(),
116 path: "{sign_in}".to_owned(),
117 handler: "sign_in_page".to_owned(),
118 permission: None,
119 }],
120 ..crate::Identity::default()
121 });
122
123 let served = shop.package_routes().identity().unwrap();
124
125 let areas: Vec<(&str, &str)> = served
126 .iter()
127 .map(|route| (route.name.as_str(), route.path.as_str()))
128 .collect();
129 assert_eq!(areas, [("sofie_identity.member.sign_in_page", "/sign-in")]);
130 }
131}