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::other("Failed to hash admin password"));
19        }
20    };
21
22    match database.database_type {
23        crate::database::DatabaseType::Sqlite => {
24            // create the users table
25            statements.push(
26                "CREATE TABLE IF NOT EXISTS Users (
27                    id INTEGER PRIMARY KEY AUTOINCREMENT,
28                    password TEXT NOT NULL,
29                    username TEXT NOT NULL UNIQUE,
30                    role_id INTEGER,
31                    FOREIGN KEY (role_id) REFERENCES Roles(id)
32                );"
33                .to_string(),
34            );
35
36            // create the roles table
37            statements.push(
38                "CREATE TABLE IF NOT EXISTS Roles (
39                    id INTEGER PRIMARY KEY AUTOINCREMENT,
40                    name TEXT NOT NULL UNIQUE
41                );"
42                .to_string(),
43            );
44
45            // create the permissions table
46            statements.push(
47                "CREATE TABLE IF NOT EXISTS Permissions (
48                    id INTEGER PRIMARY KEY AUTOINCREMENT,
49                    name TEXT NOT NULL UNIQUE,
50                    role_id INTEGER NOT NULL,
51                    FOREIGN KEY (role_id) REFERENCES Roles(id)
52                );"
53                .to_string(),
54            );
55
56            // create the sessions table
57            statements.push(
58                "CREATE TABLE IF NOT EXISTS Sessions (
59                    id INTEGER PRIMARY KEY AUTOINCREMENT,
60                    user_id INTEGER NOT NULL,
61                    session_token TEXT NOT NULL UNIQUE,
62                    session_data TEXT,
63                    expiration_date DATETIME,
64                    FOREIGN KEY (user_id) REFERENCES Users(id)
65                );"
66                .to_string(),
67            );
68
69            // add admin role
70            statements.push(format!(
71                "INSERT OR IGNORE INTO Roles (name) VALUES ('{}');",
72                "admin"
73            ));
74
75            // create default permissions
76            statements.push(format!(
77                "INSERT OR IGNORE INTO Permissions (name, role_id) VALUES ('{}', 1);",
78                "create_user"
79            ));
80
81            // create default permissions
82            statements.push(format!(
83                "INSERT OR IGNORE INTO Permissions (name, role_id) VALUES ('{}', 1);",
84                "read_user"
85            ));
86
87            // add admin user
88            statements.push(format!(
89                    "INSERT OR IGNORE INTO Users (password, username, role_id) VALUES ('{hashed_admin_password}', 'admin', 1);"
90                ));
91
92            // create the down migration
93            let mut down_statements = Vec::new();
94            down_statements.push(format!("DROP DATABASE {};", project.name));
95
96            // write the down migration to the file
97            for (idx, statement) in down_statements.iter().enumerate() {
98                write_to_file(
99                    &(&project.initial_migration_down).to_string(),
100                    statement.as_bytes(),
101                )
102                .unwrap_or_else(|why| {
103                    panic!(
104                        "Failed to write to file {}_{}: {:?}",
105                        &project.initial_migration_down,
106                        idx,
107                        why.kind()
108                    )
109                });
110            }
111        }
112        crate::database::DatabaseType::Postgres => {
113            // create the roles table
114            statements.push(
115                "
116CREATE TABLE Roles (
117    id SERIAL PRIMARY KEY,
118    name TEXT NOT NULL UNIQUE
119);"
120                .to_string(),
121            );
122
123            // create the users table
124            statements.push(
125                "
126CREATE TABLE Users (
127    id SERIAL PRIMARY KEY,
128    password TEXT NOT NULL,
129    username TEXT NOT NULL UNIQUE,
130    role_id INTEGER,
131    FOREIGN KEY (role_id) REFERENCES Roles(id)
132);"
133                .to_string(),
134            );
135
136            // create the permissions table
137            statements.push(
138                "
139CREATE TABLE Permissions (
140    id SERIAL PRIMARY KEY,
141    name TEXT NOT NULL UNIQUE,
142    role_id INTEGER NOT NULL,
143    FOREIGN KEY (role_id) REFERENCES Roles(id)
144);"
145                .to_string(),
146            );
147
148            // create the sessions table
149            statements.push(
150                "
151CREATE TABLE Sessions (
152    id SERIAL PRIMARY KEY,
153    user_id INTEGER NOT NULL,
154    session_token TEXT NOT NULL UNIQUE,
155    session_data TEXT,
156    expiration_date TIMESTAMP,
157    FOREIGN KEY (user_id) REFERENCES Users(id)
158);"
159                .to_string(),
160            );
161
162            // add admin role
163            statements.push(format!("INSERT INTO Roles (name) VALUES ('{}');", "admin"));
164
165            // create default permissions
166            statements.push(format!(
167                "INSERT INTO Permissions (name, role_id) VALUES ('{}', 1);",
168                "create_user"
169            ));
170
171            // create default permissions
172            statements.push(format!(
173                "INSERT INTO Permissions (name, role_id) VALUES ('{}', 1);",
174                "read_user"
175            ));
176
177            // add admin user
178            statements.push(
179                format!(
180                    "INSERT INTO Users (password, username, role_id) VALUES ('{hashed_admin_password}', 'admin', 1);"
181                )
182            );
183
184            // write the template to the file
185            for (idx, statement) in statements.iter().enumerate() {
186                write_to_file(
187                    &(&project.initial_migration_up).to_string(),
188                    statement.as_bytes(),
189                )
190                .unwrap_or_else(|why| {
191                    panic!(
192                        "Failed to write to file {}_{}: {:?}",
193                        &project.initial_migration_up,
194                        idx,
195                        why.kind()
196                    )
197                });
198            }
199
200            // create the down migration
201            let mut down_statements = Vec::new();
202            down_statements.push("DROP SCHEMA public CASCADE;".to_string());
203            down_statements.push("CREATE SCHEMA public;".to_string());
204
205            // write the down migration to the file
206            for (idx, statement) in down_statements.iter().enumerate() {
207                write_to_file(
208                    &(&project.initial_migration_down).to_string(),
209                    statement.as_bytes(),
210                )
211                .unwrap_or_else(|why| {
212                    panic!(
213                        "Failed to write to file {}_{}: {:?}",
214                        &project.initial_migration_down,
215                        idx,
216                        why.kind()
217                    )
218                });
219            }
220        }
221        crate::database::DatabaseType::Mysql => {
222            // create the roles table
223            statements.push(
224                "
225CREATE TABLE Roles (
226    id INT AUTO_INCREMENT PRIMARY KEY,
227    name VARCHAR(255) NOT NULL UNIQUE
228);"
229                .to_string(),
230            );
231
232            // create the users table
233            statements.push(
234                "
235CREATE TABLE Users (
236    id INT AUTO_INCREMENT PRIMARY KEY,
237    password VARCHAR(255) NOT NULL,
238    username VARCHAR(255) NOT NULL UNIQUE,
239    email VARCHAR(255) UNIQUE,
240    role_id INT,
241    FOREIGN KEY (role_id) REFERENCES Roles(id)
242);"
243                .to_string(),
244            );
245
246            // create the permissions table
247            statements.push(
248                "
249CREATE TABLE Permissions (
250    id INT AUTO_INCREMENT PRIMARY KEY,
251    name VARCHAR(255) NOT NULL UNIQUE,
252    role_id INT NOT NULL,
253    FOREIGN KEY (role_id) REFERENCES Roles(id)
254);"
255                .to_string(),
256            );
257
258            // create the sessions table
259            statements.push(
260                "
261CREATE TABLE Sessions (
262    id INT AUTO_INCREMENT PRIMARY KEY,
263    user_id INT NOT NULL,
264    session_token VARCHAR(255) NOT NULL UNIQUE,
265    session_data TEXT,
266    expiration_date DATETIME,
267    FOREIGN KEY (user_id) REFERENCES Users(id)
268);"
269                .to_string(),
270            );
271
272            // add admin role
273            statements.push(format!("INSERT INTO Roles (name) VALUES ('{}');", "admin"));
274
275            // create default permissions
276            statements.push(format!(
277                "INSERT INTO Permissions (name, role_id) VALUES ('{}', 1);",
278                "create_user"
279            ));
280
281            // create default permissions
282            statements.push(format!(
283                "INSERT INTO Permissions (name, role_id) VALUES ('{}', 1);",
284                "read_user"
285            ));
286
287            // add admin user
288            statements.push(
289                format!(
290                    "INSERT INTO Users (password, username, role_id) VALUES ('{hashed_admin_password}', 'admin', 1);"
291                )
292            );
293
294            // write the template to the file
295            for (idx, statement) in statements.iter().enumerate() {
296                write_to_file(
297                    &(&project.initial_migration_up).to_string(),
298                    statement.as_bytes(),
299                )
300                .unwrap_or_else(|why| {
301                    panic!(
302                        "Failed to write to file {}_{}: {:?}",
303                        &project.initial_migration_up,
304                        idx,
305                        why.kind()
306                    )
307                });
308            }
309
310            // create the down migration
311            let mut down_statements = Vec::new();
312            down_statements.push(format!("DROP DATABASE {};", project.name));
313
314            // write the down migration to the file
315            for (idx, statement) in down_statements.iter().enumerate() {
316                write_to_file(
317                    &(&project.initial_migration_down).to_string(),
318                    statement.as_bytes(),
319                )
320                .unwrap_or_else(|why| {
321                    panic!(
322                        "Failed to write to file {}_{}: {:?}",
323                        &project.initial_migration_down,
324                        idx,
325                        why.kind()
326                    )
327                });
328            }
329        }
330        crate::database::DatabaseType::Mongo => todo!(),
331    }
332
333    Ok(statements)
334}