๐Ÿ‘ฉโ€๐Ÿš’ Firefighters API written in Gleam!
lustre gleam

:passport_control: use "user" module for generating seed

kacaii.dev 2e0b404b 024126b3

verified
+174 -104
+1 -1
server/src/server.gleam
··· 23 23 let handler = router.handle_request(_, ctx) 24 24 25 25 let assert Ok(_) = supervision_tree.init(pog_config, handler, secret_key) 26 - let assert Ok(_) = seed.germinate(ctx.db, ctx.secret_key) 26 + let assert Ok(_) = seed.germinate(ctx, ctx.secret_key) 27 27 28 28 // All done! 29 29 process.sleep_forever()
+31 -18
server/src/server/seed.gleam
··· 2 2 import gleam/result 3 3 import pog 4 4 import server/auth 5 + import server/context 5 6 import server/seed/sql 7 + import server/user 8 + import shared/role 6 9 7 10 pub type UserTableState { 8 11 Empty 9 12 Populated 10 13 } 11 14 15 + pub type SeedError { 16 + Auth(auth.AuthError) 17 + UserError(user.UserError) 18 + Database(pog.QueryError) 19 + } 20 + 12 21 pub const full_name = "SIGO" 13 22 14 23 pub const password = "root" ··· 17 26 18 27 pub const phone = "0000000000" 19 28 20 - pub const role = sql.Admin 29 + pub const role = role.Admin 21 30 22 31 /// ๎ถง Generate a root user when starting the system for the first time 23 - pub fn germinate( 24 - db: pog.Connection, 25 - salt: String, 26 - ) -> Result(Nil, pog.QueryError) { 27 - use state <- result.try(guard(db)) 32 + pub fn germinate(ctx: context.Context, salt: String) -> Result(Nil, SeedError) { 33 + use state <- result.try(guard(ctx.db)) 28 34 use <- bool.guard(when: state == Populated, return: Ok(Nil)) 35 + let hashed = auth.hash(value: password, salt:) 29 36 30 - let hashed = auth.hash(value: password, salt:) 31 - use _ <- result.map(sql.germinate( 32 - db, 33 - full_name, 34 - role, 35 - hashed, 36 - email, 37 - phone, 38 - True, 39 - )) 37 + use _ <- result.map( 38 + user.register( 39 + ctx: ctx, 40 + user_name: full_name, 41 + user_role: role, 42 + user_password: hashed, 43 + user_email: email, 44 + user_phone: phone, 45 + is_active: True, 46 + ) 47 + |> result.map_error(UserError), 48 + ) 40 49 41 50 Nil 42 51 } 43 52 44 - fn guard(db: pog.Connection) -> Result(UserTableState, pog.QueryError) { 45 - use returned <- result.map(sql.guard(db)) 53 + fn guard(db: pog.Connection) -> Result(UserTableState, SeedError) { 54 + use returned <- result.map( 55 + sql.guard(db) 56 + |> result.map_error(Database), 57 + ) 58 + 46 59 case returned.count { 47 60 0 -> Empty 48 61 _ -> Populated
-65
server/src/server/seed/sql.gleam
··· 7 7 import gleam/dynamic/decode 8 8 import pog 9 9 10 - /// ๎ถง creates a default user when starting the system 11 - /// 12 - /// > ๐Ÿฟ๏ธ This function was generated automatically using v4.6.0 of 13 - /// > the [squirrel package](https://github.com/giacomocavalieri/squirrel). 14 - /// 15 - pub fn germinate( 16 - db: pog.Connection, 17 - arg_1: String, 18 - arg_2: UserRoleEnum, 19 - arg_3: String, 20 - arg_4: String, 21 - arg_5: String, 22 - arg_6: Bool, 23 - ) -> Result(pog.Returned(Nil), pog.QueryError) { 24 - let decoder = decode.map(decode.dynamic, fn(_) { Nil }) 25 - 26 - "-- ๎ถง creates a default user when starting the system 27 - insert into public.user_account as u ( 28 - full_name, 29 - user_role, 30 - password_hash, 31 - email, 32 - phone, 33 - is_active 34 - ) values ($1, $2, $3, $4, $5, $6); 35 - " 36 - |> pog.query 37 - |> pog.parameter(pog.text(arg_1)) 38 - |> pog.parameter(user_role_enum_encoder(arg_2)) 39 - |> pog.parameter(pog.text(arg_3)) 40 - |> pog.parameter(pog.text(arg_4)) 41 - |> pog.parameter(pog.text(arg_5)) 42 - |> pog.parameter(pog.bool(arg_6)) 43 - |> pog.returning(decoder) 44 - |> pog.execute(db) 45 - } 46 - 47 10 /// A row you get from running the `guard` query 48 11 /// defined in `./src/server/seed/sql/guard.sql`. 49 12 /// ··· 76 39 |> pog.returning(decoder) 77 40 |> pog.execute(db) 78 41 } 79 - 80 - // --- Enums ------------------------------------------------------------------- 81 - 82 - /// Corresponds to the Postgres `user_role_enum` enum. 83 - /// 84 - /// > ๐Ÿฟ๏ธ This type definition was generated automatically using v4.6.0 of the 85 - /// > [squirrel package](https://github.com/giacomocavalieri/squirrel). 86 - /// 87 - pub type UserRoleEnum { 88 - Sargeant 89 - Firefighter 90 - Developer 91 - Captain 92 - Analyst 93 - Admin 94 - } 95 - 96 - fn user_role_enum_encoder(user_role_enum) -> pog.Value { 97 - case user_role_enum { 98 - Sargeant -> "sargeant" 99 - Firefighter -> "firefighter" 100 - Developer -> "developer" 101 - Captain -> "captain" 102 - Analyst -> "analyst" 103 - Admin -> "admin" 104 - } 105 - |> pog.text 106 - }
-9
server/src/server/seed/sql/germinate.sql
··· 1 - -- ๎ถง creates a default user when starting the system 2 - insert into public.user_account as u ( 3 - full_name, 4 - user_role, 5 - password_hash, 6 - email, 7 - phone, 8 - is_active 9 - ) values ($1, $2, $3, $4, $5, $6);
+55 -9
server/src/server/user.gleam
··· 1 1 import gleam/list 2 2 import gleam/result 3 3 import pog 4 + import server/auth 5 + import server/context 4 6 import server/user/sql 5 7 import shared/role 6 8 import shared/user ··· 27 29 user.User( 28 30 uuid:, 29 31 full_name: row.full_name, 30 - role: role_from_enum(row.role), 32 + role: enum_to_role(row.role), 31 33 email: row.email, 32 34 phone: row.phone, 33 35 ) 34 36 } 35 37 36 - fn role_from_enum(enum: sql.UserRoleEnum) -> role.Role { 38 + pub fn register( 39 + ctx ctx: context.Context, 40 + user_name full_name: String, 41 + user_role role: role.Role, 42 + user_password password: String, 43 + user_email email: String, 44 + user_phone phone: String, 45 + is_active is_active: Bool, 46 + ) -> Result(user.User, UserError) { 47 + let hashed = auth.hash(value: password, salt: ctx.secret_key) 48 + let role_enum = role_to_enum(role) 49 + 50 + use returned <- result.try( 51 + sql.register(ctx.db, full_name, role_enum, hashed, email, phone, is_active) 52 + |> result.map_error(Database), 53 + ) 54 + 55 + use row <- result.map( 56 + list.first(returned.rows) 57 + |> result.replace_error(NotFound), 58 + ) 59 + 60 + user.User( 61 + uuid: row.id, 62 + role: row.user_role |> enum_to_role, 63 + full_name: row.full_name, 64 + email: row.email, 65 + phone: row.phone, 66 + ) 67 + } 68 + 69 + pub fn handle_error(err: UserError) -> wisp.Response { 70 + case err { 71 + Database(_) -> wisp.internal_server_error() 72 + NotFound -> 73 + "User not found" 74 + |> wisp.Text 75 + |> wisp.set_body(wisp.response(404), _) 76 + } 77 + } 78 + 79 + fn enum_to_role(enum: sql.UserRoleEnum) -> role.Role { 37 80 case enum { 38 81 sql.Sargeant -> role.Sargeant 39 82 sql.Firefighter -> role.Firefighter ··· 41 84 sql.Captain -> role.Captain 42 85 sql.Analyst -> role.Analyst 43 86 sql.Admin -> role.Admin 87 + sql.None -> role.None 44 88 } 45 89 } 46 90 47 - pub fn handle_error(err: UserError) -> wisp.Response { 48 - case err { 49 - Database(_) -> wisp.internal_server_error() 50 - NotFound -> 51 - "User not found" 52 - |> wisp.Text 53 - |> wisp.set_body(wisp.response(404), _) 91 + fn role_to_enum(role: role.Role) -> sql.UserRoleEnum { 92 + case role { 93 + role.Admin -> sql.Admin 94 + role.Analyst -> sql.Analyst 95 + role.Captain -> sql.Captain 96 + role.Developer -> sql.Developer 97 + role.Firefighter -> sql.Firefighter 98 + role.Sargeant -> sql.Sargeant 99 + role.None -> sql.None 54 100 } 55 101 }
+81 -1
server/src/server/user/sql.gleam
··· 58 58 |> pog.execute(db) 59 59 } 60 60 61 + /// A row you get from running the `register` query 62 + /// defined in `./src/server/user/sql/register.sql`. 63 + /// 64 + /// > ๐Ÿฟ๏ธ This type definition was generated automatically using v4.6.0 of the 65 + /// > [squirrel package](https://github.com/giacomocavalieri/squirrel). 66 + /// 67 + pub type RegisterRow { 68 + RegisterRow( 69 + id: Uuid, 70 + full_name: String, 71 + user_role: UserRoleEnum, 72 + email: String, 73 + phone: String, 74 + is_active: Bool, 75 + ) 76 + } 77 + 78 + /// ๏ˆด register an user 79 + /// 80 + /// > ๐Ÿฟ๏ธ This function was generated automatically using v4.6.0 of 81 + /// > the [squirrel package](https://github.com/giacomocavalieri/squirrel). 82 + /// 83 + pub fn register( 84 + db: pog.Connection, 85 + arg_1: String, 86 + arg_2: UserRoleEnum, 87 + arg_3: String, 88 + arg_4: String, 89 + arg_5: String, 90 + arg_6: Bool, 91 + ) -> Result(pog.Returned(RegisterRow), pog.QueryError) { 92 + let decoder = { 93 + use id <- decode.field(0, uuid_decoder()) 94 + use full_name <- decode.field(1, decode.string) 95 + use user_role <- decode.field(2, user_role_enum_decoder()) 96 + use email <- decode.field(3, decode.string) 97 + use phone <- decode.field(4, decode.string) 98 + use is_active <- decode.field(5, decode.bool) 99 + decode.success(RegisterRow( 100 + id:, 101 + full_name:, 102 + user_role:, 103 + email:, 104 + phone:, 105 + is_active:, 106 + )) 107 + } 108 + 109 + "-- ๏ˆด register an user 110 + insert into public.user_account as u 111 + (full_name, user_role, password_hash, email, phone, is_active) 112 + values ($1, $2, $3, $4, $5, $6) 113 + returning u.id, u.full_name, u.user_role, u.email, u.phone, u.is_active; 114 + " 115 + |> pog.query 116 + |> pog.parameter(pog.text(arg_1)) 117 + |> pog.parameter(user_role_enum_encoder(arg_2)) 118 + |> pog.parameter(pog.text(arg_3)) 119 + |> pog.parameter(pog.text(arg_4)) 120 + |> pog.parameter(pog.text(arg_5)) 121 + |> pog.parameter(pog.bool(arg_6)) 122 + |> pog.returning(decoder) 123 + |> pog.execute(db) 124 + } 125 + 61 126 // --- Enums ------------------------------------------------------------------- 62 127 63 128 /// Corresponds to the Postgres `user_role_enum` enum. ··· 66 131 /// > [squirrel package](https://github.com/giacomocavalieri/squirrel). 67 132 /// 68 133 pub type UserRoleEnum { 134 + None 69 135 Sargeant 70 136 Firefighter 71 137 Developer ··· 77 143 fn user_role_enum_decoder() -> decode.Decoder(UserRoleEnum) { 78 144 use user_role_enum <- decode.then(decode.string) 79 145 case user_role_enum { 146 + "none" -> decode.success(None) 80 147 "sargeant" -> decode.success(Sargeant) 81 148 "firefighter" -> decode.success(Firefighter) 82 149 "developer" -> decode.success(Developer) 83 150 "captain" -> decode.success(Captain) 84 151 "analyst" -> decode.success(Analyst) 85 152 "admin" -> decode.success(Admin) 86 - _ -> decode.failure(Sargeant, "UserRoleEnum") 153 + _ -> decode.failure(None, "UserRoleEnum") 87 154 } 155 + } 156 + 157 + fn user_role_enum_encoder(user_role_enum) -> pog.Value { 158 + case user_role_enum { 159 + None -> "none" 160 + Sargeant -> "sargeant" 161 + Firefighter -> "firefighter" 162 + Developer -> "developer" 163 + Captain -> "captain" 164 + Analyst -> "analyst" 165 + Admin -> "admin" 166 + } 167 + |> pog.text 88 168 } 89 169 90 170 // --- Encoding/decoding utils -------------------------------------------------
+5
server/src/server/user/sql/register.sql
··· 1 + -- ๏ˆด register an user 2 + insert into public.user_account as u 3 + (full_name, user_role, password_hash, email, phone, is_active) 4 + values ($1, $2, $3, $4, $5, $6) 5 + returning u.id, u.full_name, u.user_role, u.email, u.phone, u.is_active;
+1 -1
server/test/server_test.gleam
··· 21 21 let assert Ok(secret_key) = envoy.get("SECRET_KEY") 22 22 let assert Ok(pog_config) = server.read_connection_uri(db_process_name) 23 23 let assert Ok(_db_process) = pog.start(pog_config) 24 - let assert Ok(_result) = seed.germinate(db, secret_key) 25 24 26 25 let ctx = Context(priv:, db:, secret_key:) 26 + let assert Ok(_result) = seed.germinate(ctx, secret_key) 27 27 next(ctx) 28 28 }