wip: currently rewriting the project as a full stack application
tangled.org/kacaii.dev/sigo
gleam
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}