rustyroad/writers/migrations/
initial_sql_loader.rs

1use 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    // Hash the admin password
14    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            // create the users table
28            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            // create the roles table
40            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            // create the permissions table
49            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            // create the sessions table
60            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            // add admin role
73            statements.push(format!(
74                "INSERT OR IGNORE INTO Roles (name) VALUES ('{}');",
75                "admin"
76            ));
77
78            // create default permissions
79            statements.push(format!(
80                "INSERT OR IGNORE INTO Permissions (name, role_id) VALUES ('{}', 1);",
81                "create_user"
82            ));
83
84            // create default permissions
85            statements.push(format!(
86                "INSERT OR IGNORE INTO Permissions (name, role_id) VALUES ('{}', 1);",
87                "read_user"
88            ));
89
90            // add admin user
91            statements.push(format!(
92                    "INSERT OR IGNORE INTO Users (password, username, role_id) VALUES ('{hashed_admin_password}', 'admin', 1);"
93                ));
94
95            // create the down migration
96            let mut down_statements = Vec::new();
97            down_statements.push(format!("DROP DATABASE {};", project.name));
98
99            // write the down migration to the file
100            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            // create the roles table
117            statements.push(
118                "
119CREATE TABLE Roles (
120    id SERIAL PRIMARY KEY,
121    name TEXT NOT NULL UNIQUE
122);"
123                .to_string(),
124            );
125
126            // create the users table
127            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            // create the permissions table
140            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            // create the sessions table
152            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            // add admin role
166            statements.push(format!("INSERT INTO Roles (name) VALUES ('{}');", "admin"));
167
168            // create default permissions
169            statements.push(format!(
170                "INSERT INTO Permissions (name, role_id) VALUES ('{}', 1);",
171                "create_user"
172            ));
173
174            // create default permissions
175            statements.push(format!(
176                "INSERT INTO Permissions (name, role_id) VALUES ('{}', 1);",
177                "read_user"
178            ));
179
180            // add admin user
181            statements.push(
182                format!(
183                    "INSERT INTO Users (password, username, role_id) VALUES ('{hashed_admin_password}', 'admin', 1);"
184                )
185            );
186
187            // write the template to the file
188            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            // create the down migration
204            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            // write the down migration to the file
209            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            // create the roles table
226            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            // create the users table
236            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            // create the permissions table
250            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            // create the sessions table
262            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            // add admin role
276            statements.push(format!("INSERT INTO Roles (name) VALUES ('{}');", "admin"));
277
278            // create default permissions
279            statements.push(format!(
280                "INSERT INTO Permissions (name, role_id) VALUES ('{}', 1);",
281                "create_user"
282            ));
283
284            // create default permissions
285            statements.push(format!(
286                "INSERT INTO Permissions (name, role_id) VALUES ('{}', 1);",
287                "read_user"
288            ));
289
290            // add admin user
291            statements.push(
292                format!(
293                    "INSERT INTO Users (password, username, role_id) VALUES ('{hashed_admin_password}', 'admin', 1);"
294                )
295            );
296
297            // write the template to the file
298            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            // create the down migration
314            let mut down_statements = Vec::new();
315            down_statements.push(format!("DROP DATABASE {};", project.name));
316
317            // write the down migration to the file
318            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}