tetratto_core/database/
domains.rs1use crate::{
2 database::NAME_REGEX,
3 model::{
4 auth::User,
5 littleweb::{Domain, DomainData, DomainTld},
6 permissions::{FinePermission, SecondaryPermission},
7 Error, Result,
8 },
9};
10use crate::{auto_method, DataManager};
11use oiseau::{cache::Cache, execute, get, params, query_row, query_rows, PostgresRow};
12
13impl DataManager {
14 pub(crate) fn get_domain_from_row(x: &PostgresRow) -> Domain {
16 Domain {
17 id: get!(x->0(i64)) as usize,
18 created: get!(x->1(i64)) as usize,
19 owner: get!(x->2(i64)) as usize,
20 name: get!(x->3(String)),
21 tld: (get!(x->4(String)).as_str()).into(),
22 data: serde_json::from_str(&get!(x->5(String))).unwrap(),
23 }
24 }
25
26 auto_method!(get_domain_by_id(usize as i64)@get_domain_from_row -> "SELECT * FROM domains WHERE id = $1" --name="domain" --returns=Domain --cache-key-tmpl="atto.domain:{}");
27
28 pub async fn get_domain_by_name_tld(&self, name: &str, tld: &DomainTld) -> Result<Domain> {
34 let conn = match self.0.connect().await {
35 Ok(c) => c,
36 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
37 };
38
39 let res = query_row!(
40 &conn,
41 "SELECT * FROM domains WHERE name = $1 AND tld = $2",
42 &[&name, &tld.to_string()],
43 |x| { Ok(Self::get_domain_from_row(x)) }
44 );
45
46 if res.is_err() {
47 return Err(Error::GeneralNotFound("domain".to_string()));
48 }
49
50 Ok(res.unwrap())
51 }
52
53 pub async fn get_domains_by_user(&self, id: usize) -> Result<Vec<Domain>> {
58 let conn = match self.0.connect().await {
59 Ok(c) => c,
60 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
61 };
62
63 let res = query_rows!(
64 &conn,
65 "SELECT * FROM domains WHERE owner = $1 ORDER BY created DESC",
66 &[&(id as i64)],
67 |x| { Self::get_domain_from_row(x) }
68 );
69
70 if res.is_err() {
71 return Err(Error::GeneralNotFound("domain".to_string()));
72 }
73
74 Ok(res.unwrap())
75 }
76
77 const MAXIMUM_FREE_DOMAINS: usize = 10;
78
79 pub async fn create_domain(&self, data: Domain) -> Result<Domain> {
84 if data.name.trim().len() < 2 {
86 return Err(Error::DataTooShort("name".to_string()));
87 } else if data.name.len() > 128 {
88 return Err(Error::DataTooLong("name".to_string()));
89 }
90
91 let owner = self.get_user_by_id(data.owner).await?;
93
94 if !owner.permissions.check(FinePermission::SUPPORTER) {
95 let domains = self.get_domains_by_user(data.owner).await?;
96
97 if domains.len() >= Self::MAXIMUM_FREE_DOMAINS {
98 return Err(Error::MiscError(
99 "You already have the maximum number of domains you can have".to_string(),
100 ));
101 }
102 }
103
104 let regex = regex::RegexBuilder::new(NAME_REGEX)
106 .multi_line(true)
107 .build()
108 .unwrap();
109
110 if regex.captures(&data.name).is_some() {
111 return Err(Error::MiscError(
112 "Domain name contains invalid characters".to_string(),
113 ));
114 }
115
116 if self
118 .get_domain_by_name_tld(&data.name, &data.tld)
119 .await
120 .is_ok()
121 {
122 return Err(Error::MiscError(
123 "Domain + TLD already in use. Maybe try another TLD!".to_string(),
124 ));
125 }
126
127 let conn = match self.0.connect().await {
129 Ok(c) => c,
130 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
131 };
132
133 let res = execute!(
134 &conn,
135 "INSERT INTO domains VALUES ($1, $2, $3, $4, $5, $6)",
136 params![
137 &(data.id as i64),
138 &(data.created as i64),
139 &(data.owner as i64),
140 &data.name,
141 &data.tld.to_string(),
142 &serde_json::to_string(&data.data).unwrap(),
143 ]
144 );
145
146 if let Err(e) = res {
147 return Err(Error::DatabaseError(e.to_string()));
148 }
149
150 Ok(data)
151 }
152
153 pub async fn delete_domain(&self, id: usize, user: &User) -> Result<()> {
154 let domain = self.get_domain_by_id(id).await?;
155
156 if user.id != domain.owner
158 && !user
159 .secondary_permissions
160 .check(SecondaryPermission::MANAGE_DOMAINS)
161 {
162 return Err(Error::NotAllowed);
163 }
164
165 let conn = match self.0.connect().await {
167 Ok(c) => c,
168 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
169 };
170
171 let res = execute!(&conn, "DELETE FROM domains WHERE id = $1", &[&(id as i64)]);
172
173 if let Err(e) = res {
174 return Err(Error::DatabaseError(e.to_string()));
175 }
176
177 self.0.1.remove(format!("atto.domain:{}", id)).await;
179 Ok(())
180 }
181
182 auto_method!(update_domain_data(Vec<(String, DomainData)>)@get_domain_by_id:FinePermission::MANAGE_USERS; -> "UPDATE domains SET data = $1 WHERE id = $2" --serde --cache-key-tmpl="atto.domain:{}");
183}