wip: currently rewriting the project as a full stack application tangled.org/kacaii.dev/sigo
gleam
at main 202 lines 5.2 kB view raw
1import app/domain/admin/sql 2import app/domain/role 3import app/domain/user 4import app/web 5import app/web/context.{type Context} 6import gleam/dynamic/decode 7import gleam/http 8import gleam/json 9import gleam/list 10import gleam/result 11import gleam/time/timestamp 12import pog 13import wisp 14import youid/uuid 15 16/// 󰚰 Update an user information with admin privileges and 17/// return the update data as formatted JSON 18/// 19/// ## Response 20/// 21/// ```json 22/// { 23/// "id": "550e8400-e29b-41d4-a716-446655440000", 24/// "full_name": "João Silva", 25/// "email": "joao.silva@example.com", 26/// "user_role": "administrador", 27/// "registration": "20230001", 28/// "updated_at": 1698765432.123, 29/// "is_active": true 30/// } 31/// ``` 32pub fn handle_request( 33 request req: wisp.Request, 34 ctx ctx: Context, 35 id user_id: String, 36) -> wisp.Response { 37 use <- wisp.require_method(req, http.Put) 38 use body <- wisp.require_json(req) 39 40 case decode.run(body, body_decoder()) { 41 Error(err) -> web.handle_decode_error(err) 42 Ok(value) -> handle_body(req, ctx, value, user_id) 43 } 44} 45 46fn handle_body( 47 req: wisp.Request, 48 ctx: Context, 49 body: RequestBody, 50 user_id: String, 51) -> wisp.Response { 52 case query_database(req, ctx, body, user_id) { 53 Ok(body) -> wisp.json_response(body, 200) 54 Error(err) -> handle_error(body, err) 55 } 56} 57 58type RequestBody { 59 RequestBody( 60 full_name: String, 61 email: String, 62 user_role: role.Role, 63 registration: String, 64 is_active: Bool, 65 ) 66} 67 68fn body_decoder() -> decode.Decoder(RequestBody) { 69 use full_name <- decode.field("full_name", decode.string) 70 use email <- decode.field("email", decode.string) 71 use user_role <- decode.field("user_role", role.decoder()) 72 use registration <- decode.field("registration", decode.string) 73 use is_active <- decode.field("is_active", decode.bool) 74 75 decode.success(RequestBody( 76 full_name:, 77 email:, 78 user_role:, 79 registration:, 80 is_active:, 81 )) 82} 83 84type AdminUpdateUserError { 85 /// Failed to access the DataBase 86 DataBase(pog.QueryError) 87 /// User has invalid Uuid format 88 InvalidUuid(String) 89 /// Authentication / Authorization failed 90 AccessControl(user.AccessControlError) 91 /// User not found in the DataBase 92 NotFound(uuid.Uuid) 93} 94 95fn handle_error(body: RequestBody, err: AdminUpdateUserError) -> wisp.Response { 96 case err { 97 AccessControl(err) -> user.handle_access_control_error(err) 98 99 InvalidUuid(id) -> wisp.bad_request("Usuário possui Uuid inválido: " <> id) 100 101 NotFound(id) -> 102 wisp.Text("Usuário não encontrado: " <> uuid.to_string(id)) 103 |> wisp.set_body(wisp.not_found(), _) 104 105 DataBase(err) -> handle_database_error(err, body) 106 } 107} 108 109fn handle_database_error( 110 err: pog.QueryError, 111 body: RequestBody, 112) -> wisp.Response { 113 case err { 114 pog.ConstraintViolated(_, _, constraint: "user_account_email_key") -> 115 wisp.Text("Email já está sendo utilizado: " <> body.email) 116 |> wisp.set_body(wisp.response(409), _) 117 118 pog.ConstraintViolated(_, _, constraint: "user_account_registration_key") -> 119 wisp.Text("Matrícula já está sendo utilizada: " <> body.registration) 120 |> wisp.set_body(wisp.response(409), _) 121 122 pog.ConstraintViolated(_, _, constraint: "user_account_phone_key") -> { 123 "Telefone já cadastrado. Por favor, utilize um diferente" 124 |> wisp.Text 125 |> wisp.set_body(wisp.response(409), _) 126 } 127 128 err -> web.handle_database_error(err) 129 } 130} 131 132fn query_database( 133 req: wisp.Request, 134 ctx: Context, 135 body: RequestBody, 136 user_id: String, 137) -> Result(String, AdminUpdateUserError) { 138 use _ <- result.try( 139 user.check_authorization(req, [role.Admin, role.Developer]) 140 |> result.map_error(AccessControl), 141 ) 142 143 use user_uuid <- result.try( 144 uuid.from_string(user_id) 145 |> result.replace_error(InvalidUuid(user_id)), 146 ) 147 148 use returned <- result.try( 149 sql.admin_update_user( 150 ctx.db, 151 user_uuid, 152 body.full_name, 153 body.email, 154 role_to_enum(body.user_role), 155 body.registration, 156 body.is_active, 157 ) 158 |> result.map_error(DataBase), 159 ) 160 161 use row <- result.map( 162 list.first(returned.rows) 163 |> result.replace_error(NotFound(user_uuid)), 164 ) 165 166 let user_role = case row.user_role { 167 sql.Admin -> role.Admin 168 sql.Analyst -> role.Analyst 169 sql.Captain -> role.Captain 170 sql.Developer -> role.Developer 171 sql.Firefighter -> role.Firefighter 172 sql.Sargeant -> role.Sargeant 173 } 174 175 let updated_at_json = 176 json.float( 177 row.updated_at 178 |> timestamp.to_unix_seconds(), 179 ) 180 181 json.object([ 182 #("id", json.string(uuid.to_string(row.id))), 183 #("full_name", json.string(row.full_name)), 184 #("email", json.string(row.email)), 185 #("user_role", json.string(role.to_string_pt_br(user_role))), 186 #("registration", json.string(row.registration)), 187 #("updated_at", updated_at_json), 188 #("is_active", json.bool(row.is_active)), 189 ]) 190 |> json.to_string 191} 192 193fn role_to_enum(role: role.Role) { 194 case role { 195 role.Admin -> sql.Admin 196 role.Analyst -> sql.Analyst 197 role.Captain -> sql.Captain 198 role.Developer -> sql.Developer 199 role.Firefighter -> sql.Firefighter 200 role.Sargeant -> sql.Sargeant 201 } 202}