rustyroad/writers/migrations/
initial_sql_loader.rs1use crate::{database::Database, writers::write_to_file, Project};
2use bcrypt::{hash as new_hash, DEFAULT_COST};
3use color_eyre::eyre::Result;
4async fn hash_password(password: &str) -> Result<String, bcrypt::BcryptError> {
5 new_hash(password, DEFAULT_COST)
6}
7
8pub async fn load_sql_for_new_project(
9 project: &Project,
10 database: Database,
11) -> Result<Vec<String>, std::io::Error> {
12 let mut statements = Vec::new();
13 let hashed_admin_password = match hash_password("admin").await {
15 Ok(hashed_password) => hashed_password,
16 Err(err) => {
17 eprintln!("Failed to hash admin password: {:?}", err);
18 return Err(std::io::Error::new(
19 std::io::ErrorKind::Other,
20 "Failed to hash admin password",
21 ));
22 }
23 };
24
25 match database.database_type {
26 crate::database::DatabaseType::Sqlite => {
27 statements.push(
29 "CREATE TABLE IF NOT EXISTS Users (
30 id INTEGER PRIMARY KEY AUTOINCREMENT,
31 password TEXT NOT NULL,
32 username TEXT NOT NULL UNIQUE,
33 role_id INTEGER,
34 FOREIGN KEY (role_id) REFERENCES Roles(id)
35 );"
36 .to_string(),
37 );
38
39 statements.push(
41 "CREATE TABLE IF NOT EXISTS Roles (
42 id INTEGER PRIMARY KEY AUTOINCREMENT,
43 name TEXT NOT NULL UNIQUE
44 );"
45 .to_string(),
46 );
47
48 statements.push(
50 "CREATE TABLE IF NOT EXISTS Permissions (
51 id INTEGER PRIMARY KEY AUTOINCREMENT,
52 name TEXT NOT NULL UNIQUE,
53 role_id INTEGER NOT NULL,
54 FOREIGN KEY (role_id) REFERENCES Roles(id)
55 );"
56 .to_string(),
57 );
58
59 statements.push(
61 "CREATE TABLE IF NOT EXISTS Sessions (
62 id INTEGER PRIMARY KEY AUTOINCREMENT,
63 user_id INTEGER NOT NULL,
64 session_token TEXT NOT NULL UNIQUE,
65 session_data TEXT,
66 expiration_date DATETIME,
67 FOREIGN KEY (user_id) REFERENCES Users(id)
68 );"
69 .to_string(),
70 );
71
72 statements.push(format!(
74 "INSERT OR IGNORE INTO Roles (name) VALUES ('{}');",
75 "admin"
76 ));
77
78 statements.push(format!(
80 "INSERT OR IGNORE INTO Permissions (name, role_id) VALUES ('{}', 1);",
81 "create_user"
82 ));
83
84 statements.push(format!(
86 "INSERT OR IGNORE INTO Permissions (name, role_id) VALUES ('{}', 1);",
87 "read_user"
88 ));
89
90 statements.push(format!(
92 "INSERT OR IGNORE INTO Users (password, username, role_id) VALUES ('{hashed_admin_password}', 'admin', 1);"
93 ));
94
95 let mut down_statements = Vec::new();
97 down_statements.push(format!("DROP DATABASE {};", project.name));
98
99 for (idx, statement) in down_statements.iter().enumerate() {
101 write_to_file(
102 &format!("{}", &project.initial_migration_down),
103 statement.as_bytes(),
104 )
105 .unwrap_or_else(|why| {
106 panic!(
107 "Failed to write to file {}_{}: {:?}",
108 &project.initial_migration_down,
109 idx,
110 why.kind()
111 )
112 });
113 }
114 }
115 crate::database::DatabaseType::Postgres => {
116 statements.push(
118 "
119CREATE TABLE Roles (
120 id SERIAL PRIMARY KEY,
121 name TEXT NOT NULL UNIQUE
122);"
123 .to_string(),
124 );
125
126 statements.push(
128 "
129CREATE TABLE Users (
130 id SERIAL PRIMARY KEY,
131 password TEXT NOT NULL,
132 username TEXT NOT NULL UNIQUE,
133 role_id INTEGER,
134 FOREIGN KEY (role_id) REFERENCES Roles(id)
135);"
136 .to_string(),
137 );
138
139 statements.push(
141 "
142CREATE TABLE Permissions (
143 id SERIAL PRIMARY KEY,
144 name TEXT NOT NULL UNIQUE,
145 role_id INTEGER NOT NULL,
146 FOREIGN KEY (role_id) REFERENCES Roles(id)
147);"
148 .to_string(),
149 );
150
151 statements.push(
153 "
154CREATE TABLE Sessions (
155 id SERIAL PRIMARY KEY,
156 user_id INTEGER NOT NULL,
157 session_token TEXT NOT NULL UNIQUE,
158 session_data TEXT,
159 expiration_date TIMESTAMP,
160 FOREIGN KEY (user_id) REFERENCES Users(id)
161);"
162 .to_string(),
163 );
164
165 statements.push(format!("INSERT INTO Roles (name) VALUES ('{}');", "admin"));
167
168 statements.push(format!(
170 "INSERT INTO Permissions (name, role_id) VALUES ('{}', 1);",
171 "create_user"
172 ));
173
174 statements.push(format!(
176 "INSERT INTO Permissions (name, role_id) VALUES ('{}', 1);",
177 "read_user"
178 ));
179
180 statements.push(
182 format!(
183 "INSERT INTO Users (password, username, role_id) VALUES ('{hashed_admin_password}', 'admin', 1);"
184 )
185 );
186
187 for (idx, statement) in statements.iter().enumerate() {
189 write_to_file(
190 &format!("{}", &project.initial_migration_up),
191 statement.as_bytes(),
192 )
193 .unwrap_or_else(|why| {
194 panic!(
195 "Failed to write to file {}_{}: {:?}",
196 &project.initial_migration_up,
197 idx,
198 why.kind()
199 )
200 });
201 }
202
203 let mut down_statements = Vec::new();
205 down_statements.push(format!("DROP SCHEMA public CASCADE;"));
206 down_statements.push(format!("CREATE SCHEMA public;"));
207
208 for (idx, statement) in down_statements.iter().enumerate() {
210 write_to_file(
211 &format!("{}", &project.initial_migration_down),
212 statement.as_bytes(),
213 )
214 .unwrap_or_else(|why| {
215 panic!(
216 "Failed to write to file {}_{}: {:?}",
217 &project.initial_migration_down,
218 idx,
219 why.kind()
220 )
221 });
222 }
223 }
224 crate::database::DatabaseType::Mysql => {
225 statements.push(
227 "
228CREATE TABLE Roles (
229 id INT AUTO_INCREMENT PRIMARY KEY,
230 name VARCHAR(255) NOT NULL UNIQUE
231);"
232 .to_string(),
233 );
234
235 statements.push(
237 "
238CREATE TABLE Users (
239 id INT AUTO_INCREMENT PRIMARY KEY,
240 password VARCHAR(255) NOT NULL,
241 username VARCHAR(255) NOT NULL UNIQUE,
242 email VARCHAR(255) UNIQUE,
243 role_id INT,
244 FOREIGN KEY (role_id) REFERENCES Roles(id)
245);"
246 .to_string(),
247 );
248
249 statements.push(
251 "
252CREATE TABLE Permissions (
253 id INT AUTO_INCREMENT PRIMARY KEY,
254 name VARCHAR(255) NOT NULL UNIQUE,
255 role_id INT NOT NULL,
256 FOREIGN KEY (role_id) REFERENCES Roles(id)
257);"
258 .to_string(),
259 );
260
261 statements.push(
263 "
264CREATE TABLE Sessions (
265 id INT AUTO_INCREMENT PRIMARY KEY,
266 user_id INT NOT NULL,
267 session_token VARCHAR(255) NOT NULL UNIQUE,
268 session_data TEXT,
269 expiration_date DATETIME,
270 FOREIGN KEY (user_id) REFERENCES Users(id)
271);"
272 .to_string(),
273 );
274
275 statements.push(format!("INSERT INTO Roles (name) VALUES ('{}');", "admin"));
277
278 statements.push(format!(
280 "INSERT INTO Permissions (name, role_id) VALUES ('{}', 1);",
281 "create_user"
282 ));
283
284 statements.push(format!(
286 "INSERT INTO Permissions (name, role_id) VALUES ('{}', 1);",
287 "read_user"
288 ));
289
290 statements.push(
292 format!(
293 "INSERT INTO Users (password, username, role_id) VALUES ('{hashed_admin_password}', 'admin', 1);"
294 )
295 );
296
297 for (idx, statement) in statements.iter().enumerate() {
299 write_to_file(
300 &format!("{}", &project.initial_migration_up),
301 statement.as_bytes(),
302 )
303 .unwrap_or_else(|why| {
304 panic!(
305 "Failed to write to file {}_{}: {:?}",
306 &project.initial_migration_up,
307 idx,
308 why.kind()
309 )
310 });
311 }
312
313 let mut down_statements = Vec::new();
315 down_statements.push(format!("DROP DATABASE {};", project.name));
316
317 for (idx, statement) in down_statements.iter().enumerate() {
319 write_to_file(
320 &format!("{}", &project.initial_migration_down),
321 statement.as_bytes(),
322 )
323 .unwrap_or_else(|why| {
324 panic!(
325 "Failed to write to file {}_{}: {:?}",
326 &project.initial_migration_down,
327 idx,
328 why.kind()
329 )
330 });
331 }
332 }
333 crate::database::DatabaseType::Mongo => todo!(),
334 }
335
336 Ok(statements)
337}