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

:recycle: no need to pattern match on the http method

+1 -1
dev/dummy.gleam
··· 296 296 row.id 297 297 } 298 298 True -> { 299 - let assert Ok(returned) = o_sql.resolve_occurrence(ctx.db, occ) 299 + let assert Ok(returned) = o_sql.close_occurrence(ctx.db, occ) 300 300 let assert Ok(row) = list.first(returned.rows) 301 301 row.id 302 302 }
+2 -2
src/app/domain/brigade/get_brigade_members.gleam
··· 38 38 /// } 39 39 /// ] 40 40 pub fn handle_request( 41 - request request: wisp.Request, 41 + request req: wisp.Request, 42 42 ctx ctx: Context, 43 43 id brigade_id: String, 44 44 ) -> wisp.Response { 45 - use <- wisp.require_method(request, http.Get) 45 + use <- wisp.require_method(req, http.Get) 46 46 47 47 case query_brigade_members(ctx, brigade_id) { 48 48 Ok(body) -> wisp.json_response(body, 200)
+6 -8
src/app/domain/brigade/sql.gleam
··· 41 41 " 42 42 |> pog.query 43 43 |> pog.parameter(pog.text(uuid.to_string(arg_1))) 44 - |> pog.parameter(pog.array( 45 - fn(value) { pog.text(uuid.to_string(value)) }, 46 - arg_2, 47 - )) 44 + |> pog.parameter( 45 + pog.array(fn(value) { pog.text(uuid.to_string(value)) }, arg_2), 46 + ) 48 47 |> pog.returning(decoder) 49 48 |> pog.execute(db) 50 49 } ··· 354 353 " 355 354 |> pog.query 356 355 |> pog.parameter(pog.text(uuid.to_string(arg_1))) 357 - |> pog.parameter(pog.array( 358 - fn(value) { pog.text(uuid.to_string(value)) }, 359 - arg_2, 360 - )) 356 + |> pog.parameter( 357 + pog.array(fn(value) { pog.text(uuid.to_string(value)) }, arg_2), 358 + ) 361 359 |> pog.returning(decoder) 362 360 |> pog.execute(db) 363 361 }
+2 -2
src/app/domain/dashboard.gleam
··· 29 29 /// } 30 30 /// ``` 31 31 pub fn handle_request( 32 - request request: wisp.Request, 32 + request req: wisp.Request, 33 33 ctx ctx: Context, 34 34 ) -> wisp.Response { 35 - use <- wisp.require_method(request, http.Get) 35 + use <- wisp.require_method(req, http.Get) 36 36 37 37 case query_dashboard_stats(ctx) { 38 38 Ok(body) -> wisp.json_response(body, 200)
+4 -12
src/app/domain/data_analysis/sql.gleam
··· 131 131 "medic_emergency" -> decode.success(MedicEmergency) 132 132 _ -> decode.failure(Other, "OccurrenceCategoryEnum") 133 133 } 134 - } 135 - 136 - /// Corresponds to the Postgres `occurrence_priority_enum` enum. 134 + }/// Corresponds to the Postgres `occurrence_priority_enum` enum. 137 135 /// 138 136 /// > ๐Ÿฟ๏ธ This type definition was generated automatically using v4.6.0 of the 139 137 /// > [squirrel package](https://github.com/giacomocavalieri/squirrel). ··· 152 150 "low" -> decode.success(Low) 153 151 _ -> decode.failure(High, "OccurrencePriorityEnum") 154 152 } 155 - } 156 - 157 - /// Corresponds to the Postgres `occurrence_subcategory_enum` enum. 153 + }/// Corresponds to the Postgres `occurrence_subcategory_enum` enum. 158 154 /// 159 155 /// > ๐Ÿฟ๏ธ This type definition was generated automatically using v4.6.0 of the 160 156 /// > [squirrel package](https://github.com/giacomocavalieri/squirrel). ··· 178 174 HeartStop 179 175 } 180 176 181 - fn occurrence_subcategory_enum_decoder() -> decode.Decoder( 182 - OccurrenceSubcategoryEnum, 183 - ) { 177 + fn occurrence_subcategory_enum_decoder() -> decode.Decoder(OccurrenceSubcategoryEnum) { 184 178 use occurrence_subcategory_enum <- decode.then(decode.string) 185 179 case occurrence_subcategory_enum { 186 180 "injured_animal" -> decode.success(InjuredAnimal) ··· 201 195 "heart_stop" -> decode.success(HeartStop) 202 196 _ -> decode.failure(InjuredAnimal, "OccurrenceSubcategoryEnum") 203 197 } 204 - } 205 - 206 - /// Corresponds to the Postgres `user_role_enum` enum. 198 + }/// Corresponds to the Postgres `user_role_enum` enum. 207 199 /// 208 200 /// > ๐Ÿฟ๏ธ This type definition was generated automatically using v4.6.0 of the 209 201 /// > [squirrel package](https://github.com/giacomocavalieri/squirrel).
+113
src/app/domain/occurrence/close_occurrence.gleam
··· 1 + import app/domain/occurrence 2 + import app/domain/occurrence/sql 3 + import app/domain/user 4 + import app/web 5 + import app/web/context.{type Context} 6 + import app/web/socket/message as msg 7 + import gleam/http 8 + import gleam/json 9 + import gleam/list 10 + import gleam/result 11 + import gleam/time/timestamp 12 + import group_registry 13 + import pog 14 + import wisp 15 + import youid/uuid 16 + 17 + /// Resolving a occurence can fail 18 + type ResolveOccurrenceError { 19 + /// Occurrence has invalid Uuid format 20 + InvalidUuid(String) 21 + /// Occurrence was not found in the DataBase 22 + OccurrenceNotFound(uuid.Uuid) 23 + /// An error occurred whe naccessing the DataBase 24 + DataBase(pog.QueryError) 25 + /// Errors related to authentication / authorization 26 + AccessControl(user.AuthenticationError) 27 + } 28 + 29 + /// ๓ฐšฐ Updates the `resolved_at` field of a occurrence 30 + /// 31 + /// ```jsonc 32 + /// { 33 + /// "id": "a32fb57f-b547-434d-a5d6-2a2c96cddb20", 34 + /// "resolved_at": 1762889998.0, // or null 35 + /// "updated_at": 1762889998.0 36 + /// } 37 + /// ```` 38 + pub fn handle_request( 39 + request req: wisp.Request, 40 + ctx ctx: Context, 41 + id occurrence_id: String, 42 + ) -> wisp.Response { 43 + use <- wisp.require_method(req, http.Post) 44 + 45 + case close_occurrence(req, ctx, occurrence_id) { 46 + Error(err) -> handle_error(err) 47 + Ok(body) -> wisp.json_response(body, 200) 48 + } 49 + } 50 + 51 + fn close_occurrence( 52 + req: wisp.Request, 53 + ctx: Context, 54 + occ_id: String, 55 + ) -> Result(String, ResolveOccurrenceError) { 56 + use _ <- result.try( 57 + user.extract_uuid(request: req, cookie_name: user.uuid_cookie_name) 58 + |> result.map_error(AccessControl), 59 + ) 60 + 61 + use target <- result.try( 62 + uuid.from_string(occ_id) 63 + |> result.replace_error(InvalidUuid(occ_id)), 64 + ) 65 + 66 + use returned <- result.try( 67 + sql.close_occurrence(ctx.db, target) 68 + |> result.map_error(DataBase), 69 + ) 70 + 71 + use row <- result.map( 72 + list.first(returned.rows) 73 + |> result.replace_error(OccurrenceNotFound(target)), 74 + ) 75 + 76 + // ๏ผ Broadcast to all assigned users ---------------------------------------- 77 + let _ = 78 + occurrence.broadcast( 79 + ctx:, 80 + registry: group_registry.get_registry(ctx.registry_name), 81 + occurrence: row.id, 82 + message: msg.Domain(msg.OccurrenceResolved( 83 + id: row.id, 84 + when: row.resolved_at, 85 + )), 86 + ) 87 + 88 + // RESPONSE 89 + let timestamp_json = 90 + json.nullable(row.resolved_at, fn(time) { 91 + timestamp.to_unix_seconds(time) |> json.float 92 + }) 93 + 94 + json.to_string( 95 + json.object([ 96 + #("id", json.string(uuid.to_string(row.id))), 97 + #("resolved_at", timestamp_json), 98 + #("updated_at", json.float(timestamp.to_unix_seconds(row.updated_at))), 99 + ]), 100 + ) 101 + } 102 + 103 + fn handle_error(err: ResolveOccurrenceError) -> wisp.Response { 104 + case err { 105 + AccessControl(err) -> user.handle_authentication_error(err) 106 + DataBase(err) -> web.handle_database_error(err) 107 + InvalidUuid(id) -> 108 + wisp.bad_request("Ocorrรชncia possui Uuid invรกlido: " <> id) 109 + OccurrenceNotFound(occ_id) -> 110 + wisp.Text("Ocorrรชncia nรฃo encontrada: " <> uuid.to_string(occ_id)) 111 + |> wisp.set_body(wisp.not_found(), _) 112 + } 113 + }
+2 -2
src/app/domain/occurrence/get_ocurrences_by_applicant.gleam
··· 22 22 /// ๓ฐกฆ Find all occurrences/applications associated with a specific user 23 23 /// returns them as formatted JSON data 24 24 pub fn handle_request( 25 - request request: wisp.Request, 25 + request req: wisp.Request, 26 26 ctx ctx: Context, 27 27 id user_id: String, 28 28 ) -> wisp.Response { 29 - use <- wisp.require_method(request, http.Get) 29 + use <- wisp.require_method(req, http.Get) 30 30 31 31 case query_occurrences(ctx, user_id) { 32 32 Ok(resp) -> wisp.json_response(resp, 200)
+4 -4
src/app/domain/occurrence/register_new_occurrence.gleam
··· 41 41 /// } 42 42 /// ``` 43 43 pub fn handle_request( 44 - request request: wisp.Request, 44 + request req: wisp.Request, 45 45 ctx ctx: Context, 46 46 ) -> wisp.Response { 47 - use <- wisp.require_method(request, http.Post) 48 - use body <- wisp.require_json(request) 47 + use <- wisp.require_method(req, http.Post) 48 + use body <- wisp.require_json(req) 49 49 50 50 case decode.run(body, body_decoder()) { 51 51 Error(err) -> web.handle_decode_error(err) 52 - Ok(body) -> handle_body(request:, ctx:, body:) 52 + Ok(body) -> handle_body(request: req, ctx:, body:) 53 53 } 54 54 } 55 55
+58 -64
src/app/domain/occurrence/sql.gleam
··· 41 41 " 42 42 |> pog.query 43 43 |> pog.parameter(pog.text(uuid.to_string(arg_1))) 44 - |> pog.parameter(pog.array( 45 - fn(value) { pog.text(uuid.to_string(value)) }, 46 - arg_2, 47 - )) 44 + |> pog.parameter( 45 + pog.array(fn(value) { pog.text(uuid.to_string(value)) }, arg_2), 46 + ) 47 + |> pog.returning(decoder) 48 + |> pog.execute(db) 49 + } 50 + 51 + /// A row you get from running the `close_occurrence` query 52 + /// defined in `./src/app/domain/occurrence/sql/close_occurrence.sql`. 53 + /// 54 + /// > ๐Ÿฟ๏ธ This type definition was generated automatically using v4.6.0 of the 55 + /// > [squirrel package](https://github.com/giacomocavalieri/squirrel). 56 + /// 57 + pub type CloseOccurrenceRow { 58 + CloseOccurrenceRow( 59 + id: Uuid, 60 + resolved_at: Option(Timestamp), 61 + updated_at: Timestamp, 62 + ) 63 + } 64 + 65 + /// ๓ฐšฐ Mark a occurrence as resolved 66 + /// 67 + /// > ๐Ÿฟ๏ธ This function was generated automatically using v4.6.0 of 68 + /// > the [squirrel package](https://github.com/giacomocavalieri/squirrel). 69 + /// 70 + pub fn close_occurrence( 71 + db: pog.Connection, 72 + arg_1: Uuid, 73 + ) -> Result(pog.Returned(CloseOccurrenceRow), pog.QueryError) { 74 + let decoder = { 75 + use id <- decode.field(0, uuid_decoder()) 76 + use resolved_at <- decode.field(1, decode.optional(pog.timestamp_decoder())) 77 + use updated_at <- decode.field(2, pog.timestamp_decoder()) 78 + decode.success(CloseOccurrenceRow(id:, resolved_at:, updated_at:)) 79 + } 80 + 81 + "-- ๓ฐšฐ Mark a occurrence as resolved 82 + update public.occurrence 83 + set 84 + resolved_at = current_timestamp, 85 + updated_at = current_timestamp 86 + where id = $1 87 + returning 88 + id, 89 + resolved_at, 90 + updated_at; 91 + " 92 + |> pog.query 93 + |> pog.parameter(pog.text(uuid.to_string(arg_1))) 48 94 |> pog.returning(decoder) 49 95 |> pog.execute(db) 50 96 } ··· 465 511 " 466 512 |> pog.query 467 513 |> pog.parameter(pog.text(uuid.to_string(arg_1))) 468 - |> pog.parameter(pog.array( 469 - fn(value) { pog.text(uuid.to_string(value)) }, 470 - arg_2, 471 - )) 472 - |> pog.returning(decoder) 473 - |> pog.execute(db) 474 - } 475 - 476 - /// A row you get from running the `resolve_occurrence` query 477 - /// defined in `./src/app/domain/occurrence/sql/resolve_occurrence.sql`. 478 - /// 479 - /// > ๐Ÿฟ๏ธ This type definition was generated automatically using v4.6.0 of the 480 - /// > [squirrel package](https://github.com/giacomocavalieri/squirrel). 481 - /// 482 - pub type ResolveOccurrenceRow { 483 - ResolveOccurrenceRow( 484 - id: Uuid, 485 - resolved_at: Option(Timestamp), 486 - updated_at: Timestamp, 514 + |> pog.parameter( 515 + pog.array(fn(value) { pog.text(uuid.to_string(value)) }, arg_2), 487 516 ) 488 - } 489 - 490 - /// ๓ฐšฐ Mark a occurrence as resolved 491 - /// 492 - /// > ๐Ÿฟ๏ธ This function was generated automatically using v4.6.0 of 493 - /// > the [squirrel package](https://github.com/giacomocavalieri/squirrel). 494 - /// 495 - pub fn resolve_occurrence( 496 - db: pog.Connection, 497 - arg_1: Uuid, 498 - ) -> Result(pog.Returned(ResolveOccurrenceRow), pog.QueryError) { 499 - let decoder = { 500 - use id <- decode.field(0, uuid_decoder()) 501 - use resolved_at <- decode.field(1, decode.optional(pog.timestamp_decoder())) 502 - use updated_at <- decode.field(2, pog.timestamp_decoder()) 503 - decode.success(ResolveOccurrenceRow(id:, resolved_at:, updated_at:)) 504 - } 505 - 506 - "-- ๓ฐšฐ Mark a occurrence as resolved 507 - update public.occurrence 508 - set 509 - resolved_at = current_timestamp, 510 - updated_at = current_timestamp 511 - where id = $1 512 - returning 513 - id, 514 - resolved_at, 515 - updated_at; 516 - " 517 - |> pog.query 518 - |> pog.parameter(pog.text(uuid.to_string(arg_1))) 519 517 |> pog.returning(decoder) 520 518 |> pog.execute(db) 521 519 } ··· 553 551 MedicEmergency -> "medic_emergency" 554 552 } 555 553 |> pog.text 556 - } 557 - 558 - /// Corresponds to the Postgres `occurrence_priority_enum` enum. 554 + }/// Corresponds to the Postgres `occurrence_priority_enum` enum. 559 555 /// 560 556 /// > ๐Ÿฟ๏ธ This type definition was generated automatically using v4.6.0 of the 561 557 /// > [squirrel package](https://github.com/giacomocavalieri/squirrel). ··· 583 579 Low -> "low" 584 580 } 585 581 |> pog.text 586 - } 587 - 588 - /// Corresponds to the Postgres `occurrence_subcategory_enum` enum. 582 + }/// Corresponds to the Postgres `occurrence_subcategory_enum` enum. 589 583 /// 590 584 /// > ๐Ÿฟ๏ธ This type definition was generated automatically using v4.6.0 of the 591 585 /// > [squirrel package](https://github.com/giacomocavalieri/squirrel). ··· 609 603 HeartStop 610 604 } 611 605 612 - fn occurrence_subcategory_enum_decoder() -> decode.Decoder( 613 - OccurrenceSubcategoryEnum, 614 - ) { 606 + fn occurrence_subcategory_enum_decoder() -> decode.Decoder(OccurrenceSubcategoryEnum) { 615 607 use occurrence_subcategory_enum <- decode.then(decode.string) 616 608 case occurrence_subcategory_enum { 617 609 "injured_animal" -> decode.success(InjuredAnimal) ··· 634 626 } 635 627 } 636 628 637 - fn occurrence_subcategory_enum_encoder(occurrence_subcategory_enum) -> pog.Value { 629 + fn occurrence_subcategory_enum_encoder( 630 + occurrence_subcategory_enum, 631 + ) -> pog.Value { 638 632 case occurrence_subcategory_enum { 639 633 InjuredAnimal -> "injured_animal" 640 634 Flood -> "flood"
src/app/domain/occurrence/sql/resolve_occurrence.sql src/app/domain/occurrence/sql/close_occurrence.sql
+4 -67
src/app/domain/occurrence/update_occurrence_status.gleam src/app/domain/occurrence/reopen_occurrence.gleam
··· 40 40 ctx ctx: Context, 41 41 id occurrence_id: String, 42 42 ) -> wisp.Response { 43 - case req.method { 44 - // Mark an occurrence as resolved 45 - http.Post -> 46 - case resolve_occurrence(req, ctx, occurrence_id) { 47 - Error(err) -> handle_error(err) 48 - Ok(body) -> wisp.json_response(body, 200) 49 - } 50 - 51 - // Reopen a resolved occurrence 52 - http.Delete -> 53 - case reopen_occurrence(req, ctx, occurrence_id) { 54 - Error(err) -> handle_error(err) 55 - Ok(body) -> wisp.json_response(body, 200) 56 - } 43 + use <- wisp.require_method(req, http.Post) 57 44 58 - _ -> wisp.method_not_allowed([http.Post, http.Delete]) 45 + case reopen_occurrence(req, ctx, occurrence_id) { 46 + Error(err) -> handle_error(err) 47 + Ok(body) -> wisp.json_response(body, 200) 59 48 } 60 - } 61 - 62 - fn resolve_occurrence( 63 - req: wisp.Request, 64 - ctx: Context, 65 - occ_id: String, 66 - ) -> Result(String, ResolveOccurrenceError) { 67 - use _ <- result.try( 68 - user.extract_uuid(request: req, cookie_name: user.uuid_cookie_name) 69 - |> result.map_error(AccessControl), 70 - ) 71 - 72 - use target <- result.try( 73 - uuid.from_string(occ_id) 74 - |> result.replace_error(InvalidUuid(occ_id)), 75 - ) 76 - 77 - use returned <- result.try( 78 - sql.resolve_occurrence(ctx.db, target) 79 - |> result.map_error(DataBase), 80 - ) 81 - 82 - use row <- result.map( 83 - list.first(returned.rows) 84 - |> result.replace_error(OccurrenceNotFound(target)), 85 - ) 86 - 87 - // ๏ผ Broadcast to all assigned users ---------------------------------------- 88 - let _ = 89 - occurrence.broadcast( 90 - ctx:, 91 - registry: group_registry.get_registry(ctx.registry_name), 92 - occurrence: row.id, 93 - message: msg.Domain(msg.OccurrenceResolved( 94 - id: row.id, 95 - when: row.resolved_at, 96 - )), 97 - ) 98 - 99 - // RESPONSE 100 - let timestamp_json = 101 - json.nullable(row.resolved_at, fn(time) { 102 - timestamp.to_unix_seconds(time) |> json.float 103 - }) 104 - 105 - json.to_string( 106 - json.object([ 107 - #("id", json.string(uuid.to_string(row.id))), 108 - #("resolved_at", timestamp_json), 109 - #("updated_at", json.float(timestamp.to_unix_seconds(row.updated_at))), 110 - ]), 111 - ) 112 49 } 113 50 114 51 fn reopen_occurrence(
+2 -2
src/app/domain/role/get_role_list.gleam
··· 17 17 /// ``` 18 18 /// 19 19 pub fn handle_request( 20 - request request: wisp.Request, 20 + request req: wisp.Request, 21 21 ctx ctx: Context, 22 22 ) -> wisp.Response { 23 - use <- wisp.require_method(request, http.Get) 23 + use <- wisp.require_method(req, http.Get) 24 24 25 25 case query_user_roles(ctx) { 26 26 Ok(body) -> wisp.json_response(body, 200)
+3 -3
src/app/domain/user/get_user_profile.gleam
··· 27 27 /// } 28 28 /// ``` 29 29 pub fn handle_request( 30 - request request: wisp.Request, 30 + request req: wisp.Request, 31 31 ctx ctx: Context, 32 32 ) -> wisp.Response { 33 - use <- wisp.require_method(request, http.Get) 33 + use <- wisp.require_method(req, http.Get) 34 34 35 35 // ๓ฐกฆ Query the database 36 - case query_user_data(ctx, request) { 36 + case query_user_data(ctx, req) { 37 37 // ๏†ˆ Handle possible errors 38 38 Error(err) -> handle_error(err) 39 39 // ๓ฑƒœ Send the response to the client
+10 -11
src/app/domain/user/login.gleam
··· 10 10 import argus 11 11 import formal/form 12 12 import gleam/float 13 + import gleam/http 13 14 import gleam/http/response 14 15 import gleam/json 15 16 import gleam/list ··· 50 51 /// ```sh 51 52 /// set-cookie: USER_ID=0199b58a-acb0-70a8-9de7-0b65a03b8743 52 53 /// ``` 53 - pub fn handle_request(request request: wisp.Request, ctx ctx: Context) { 54 - use form_data <- wisp.require_form(request) 54 + pub fn handle_request(request req: wisp.Request, ctx ctx: Context) { 55 + use <- wisp.require_method(req, http.Post) 56 + 57 + use form_data <- wisp.require_form(req) 55 58 let form_result = 56 59 login_form() 57 60 |> form.add_values(form_data.values) ··· 61 64 Error(_) -> wisp.unprocessable_content() 62 65 Ok(data) -> { 63 66 let cookie = user.uuid_cookie_name 64 - handle_login(request, ctx, data, cookie) 67 + handle_login(req, ctx, data, cookie) 65 68 } 66 69 } 67 70 } 68 71 69 72 fn handle_login( 70 - request: wisp.Request, 73 + req: wisp.Request, 71 74 ctx: Context, 72 75 data: RequestBody, 73 76 name: String, 74 77 ) -> response.Response(wisp.Body) { 75 78 case query_database(data, ctx) { 76 79 Error(err) -> handle_error(err) 77 - Ok(resp) -> set_token(resp, request, name) 80 + Ok(resp) -> set_token(resp, req, name) 78 81 } 79 82 } 80 83 81 - fn set_token( 82 - resp: LoginToken, 83 - request: wisp.Request, 84 - name: String, 85 - ) -> wisp.Response { 84 + fn set_token(resp: LoginToken, req: wisp.Request, name: String) -> wisp.Response { 86 85 let response = wisp.json_response(resp.body, 200) 87 86 let value = uuid.to_string(resp.user_id) 88 87 let security = wisp.Signed ··· 92 91 |> duration.to_seconds 93 92 |> float.round 94 93 95 - wisp.set_cookie(response:, request:, name:, value:, security:, max_age:) 94 + wisp.set_cookie(response:, request: req, name:, value:, security:, max_age:) 96 95 } 97 96 98 97 /// ๓ฑ A form that decodes the `LogIn` value.
+2
src/app/domain/user/signup.gleam
··· 13 13 import app/web/context.{type Context} 14 14 import argus 15 15 import formal/form 16 + import gleam/http 16 17 import gleam/json 17 18 import gleam/list 18 19 import gleam/result ··· 31 32 request req: wisp.Request, 32 33 ctx ctx: Context, 33 34 ) -> wisp.Response { 35 + use <- wisp.require_method(req, http.Post) 34 36 use form_data <- wisp.require_form(req) 35 37 let form_result = 36 38 signup_form()
+4 -4
src/app/domain/user/update_user_password.gleam
··· 20 20 /// 21 21 /// ๎ธฌ Extracts the user UUID form the request's Cookies 22 22 pub fn handle_request( 23 - request request: wisp.Request, 23 + request req: wisp.Request, 24 24 ctx ctx: Context, 25 25 ) -> wisp.Response { 26 - use <- wisp.require_method(request, http.Put) 27 - use form_data <- wisp.require_form(request) 26 + use <- wisp.require_method(req, http.Put) 27 + use form_data <- wisp.require_form(req) 28 28 let form_result = 29 29 update_password_form() 30 30 |> form.add_values(form_data.values) 31 31 |> form.run() 32 32 33 33 case form_result { 34 - Ok(form_data) -> handle_form_data(request:, ctx:, form_data:) 34 + Ok(form_data) -> handle_form_data(request: req, ctx:, form_data:) 35 35 Error(_) -> { 36 36 let resp = wisp.unprocessable_content() 37 37
+34 -38
src/app/http_router.gleam
··· 18 18 import app/domain/data_analysis/analysis_occurrence_volume 19 19 import app/domain/notification/get_notification_preferences 20 20 import app/domain/notification/update_notification_preferences 21 + import app/domain/occurrence/close_occurrence 21 22 import app/domain/occurrence/delete_occurrence 22 23 import app/domain/occurrence/get_ocurrences_by_applicant 23 24 import app/domain/occurrence/register_new_occurrence 24 - import app/domain/occurrence/update_occurrence_status 25 + import app/domain/occurrence/reopen_occurrence 25 26 import app/domain/role/get_role_list 26 27 import app/domain/user/delete_user 27 28 import app/domain/user/get_all_user_profiles ··· 34 35 import app/domain/user/update_user_status 35 36 import app/web 36 37 import app/web/context.{type Context} 37 - import gleam/http 38 38 import wisp 39 39 40 40 /// ๓ฑ‚‡ Main request router - matches HTTP methods and paths to appropriate handlers ··· 44 44 ctx ctx: Context, 45 45 ) -> wisp.Response { 46 46 use request <- web.middleware(request:, context: ctx) 47 - case request.method, wisp.path_segments(request) { 47 + case wisp.path_segments(request) { 48 48 // ๎™ฒ Security routes ------------------------------------------------- 49 - http.Post, ["user", "login"] -> login.handle_request(request:, ctx:) 49 + ["user", "login"] -> login.handle_request(request:, ctx:) 50 50 51 - http.Put, ["user", "password"] -> 52 - update_user_password.handle_request(request:, ctx:) 51 + ["user", "password"] -> update_user_password.handle_request(request:, ctx:) 53 52 54 53 // ๎™ฒ Admin routes --------------------------------------------------------- 55 - http.Post, ["admin", "setup"] -> 56 - setup_first_admin.handle_request(request:, ctx:) 54 + ["admin", "setup"] -> setup_first_admin.handle_request(request:, ctx:) 57 55 58 - http.Post, ["admin", "signup"] -> signup.handle_request(request:, ctx:) 56 + ["admin", "signup"] -> signup.handle_request(request:, ctx:) 59 57 60 - http.Get, ["admin", "users"] -> 61 - get_all_user_profiles.handle_request(request:, ctx:) 58 + ["admin", "users"] -> get_all_user_profiles.handle_request(request:, ctx:) 62 59 63 - http.Put, ["admin", "users", id] -> 60 + ["admin", "users", id, "update"] -> 64 61 admin_update_user.handle_request(request:, ctx:, id:) 65 62 66 - http.Delete, ["admin", "users", id] -> 63 + ["admin", "users", id, "delete"] -> 67 64 delete_user.handle_request(request:, ctx:, id:) 68 65 69 - http.Put, ["admin", "users", id, "status"] -> 66 + ["admin", "users", id, "status"] -> 70 67 update_user_status.handle_request(request:, ctx:, id:) 71 68 72 - http.Get, ["admin", "teams"] -> 73 - get_all_brigades.handle_request(request:, ctx:) 69 + ["admin", "brigade"] -> get_all_brigades.handle_request(request:, ctx:) 74 70 75 - http.Post, ["admin", "teams"] -> 71 + ["admin", "brigade", "new"] -> 76 72 register_new_brigade.handle_request(request:, ctx:) 77 73 78 - http.Put, ["admin", "teams", id, "status"] -> 74 + ["admin", "brigade", id, "status"] -> 79 75 update_brigade_status.handle_request(request:, ctx:, id:) 80 76 81 - http.Delete, ["admin", "teams", id] -> 77 + ["admin", "brigade", id] -> 82 78 delete_brigade.handle_request(request:, ctx:, id:) 83 79 84 80 // ๓ฐจ‡ Dashboard stats ------------------------------------------------------ 85 - http.Get, ["dashboard", "stats"] -> dashboard.handle_request(request:, ctx:) 81 + ["dashboard", "stats"] -> dashboard.handle_request(request:, ctx:) 86 82 87 83 // ๓ฐ•ฎ Data analysis routes 88 - http.Get, ["analysis", "occurrence"] -> 84 + ["analysis", "occurrence"] -> 89 85 analysis_occurrence_volume.handle_request(request:, ctx:) 90 86 91 87 // ๏€‡ User data routes ----------------------------------------------------- 92 - http.Get, ["user", id, "occurrences"] -> 88 + ["user", id, "occurrences"] -> 93 89 get_ocurrences_by_applicant.handle_request(request:, ctx:, id:) 94 90 95 - http.Get, ["user", "profile"] -> 96 - get_user_profile.handle_request(request:, ctx:) 91 + ["user", "profile"] -> get_user_profile.handle_request(request:, ctx:) 97 92 98 - http.Put, ["user", "profile"] -> 93 + ["user", "profile", "update"] -> 99 94 update_user_profile.handle_request(request:, ctx:) 100 95 101 - http.Get, ["user", id, "crew_members"] -> 96 + ["user", id, "crew_members"] -> 102 97 get_crew_members.handle_request(request:, ctx:, id:) 103 98 104 99 // ๏‰บ Notification routes -------------------------------------------------- 105 - http.Get, ["user", "notification_preferences"] -> 100 + ["user", "notification_preferences"] -> 106 101 get_notification_preferences.handle_request(request:, ctx:) 107 102 108 - http.Put, ["user", "notification_preferences"] -> 103 + ["user", "notification_preferences", "update"] -> 109 104 update_notification_preferences.handle_request(request:, ctx:) 110 105 111 106 // ๓ฐž Occurrence routes ---------------------------------------------------- 112 - http.Post, ["occurrence", "new"] -> 107 + ["occurrence", "new"] -> 113 108 register_new_occurrence.handle_request(request:, ctx:) 114 109 115 - http.Delete, ["occurrence", id] -> 116 - delete_occurrence.handle_request(request:, ctx:, id:) 110 + ["occurrence", id] -> delete_occurrence.handle_request(request:, ctx:, id:) 117 111 118 - http.Post, ["occurrence", "resolved_at", id] 119 - | http.Delete, ["occurrence", "resolved_at", id] 120 - -> update_occurrence_status.handle_request(request:, ctx:, id:) 112 + ["occurrence", "close", id] -> 113 + close_occurrence.handle_request(request:, ctx:, id:) 114 + 115 + ["occurrence", "reopen", id] -> 116 + reopen_occurrence.handle_request(request:, ctx:, id:) 121 117 122 118 // ๓ฐขซ Brigade routes ------------------------------------------------------- 123 - http.Get, ["brigade", id, "members"] -> 119 + ["brigade", id, "members"] -> 124 120 get_brigade_members.handle_request(request:, ctx:, id:) 125 121 126 122 // ๎พ„ Role routes ---------------------------------------------------------- 127 - http.Get, ["user", "roles"] -> get_role_list.handle_request(request:, ctx:) 123 + ["user", "roles"] -> get_role_list.handle_request(request:, ctx:) 128 124 129 125 // Fallback routes --------------------------------------------------------- 130 - _, [] -> wisp.ok() |> wisp.html_body("<h2>๐ŸŒ </h2>") 131 - _, _ -> wisp.not_found() 126 + [] -> wisp.ok() |> wisp.html_body("<h2>๐ŸŒ </h2>") 127 + _ -> wisp.not_found() 132 128 } 133 129 }
+1 -1
test/admin_test.gleam
··· 18 18 19 19 // DUMMY USER ---------------------------------------------------------------- 20 20 let dummy_user_id = dummy.random_user(ctx.db) 21 - let path = "/admin/users/" <> uuid.to_string(dummy_user_id) 21 + let path = "/admin/users/" <> uuid.to_string(dummy_user_id) <> "/update" 22 22 23 23 // Data 24 24 let new_full_name = "wibble"
+2 -2
test/brigade_test.gleam
··· 13 13 import youid/uuid 14 14 15 15 pub fn register_new_brigade_test() { 16 - let path = "/admin/teams" 16 + let path = "/admin/brigade/new" 17 17 let ctx = app_test.global_data() 18 18 use _ <- list.each(list.range(1, app_test.n_tests)) 19 19 ··· 161 161 pub fn get_all_brigades_test() { 162 162 let ctx = app_test.global_data() 163 163 use _ <- list.each(list.range(1, app_test.n_tests)) 164 - let path = "/admin/teams" 164 + let path = "/admin/brigade" 165 165 166 166 // ๏€‡ DUMMY LEADER ----------------------------------------------------------- 167 167 let leader_id = dummy.random_user(ctx.db)
+8 -8
test/occurrence_test.gleam
··· 279 279 let assert Ok(_) = dev_sql.soft_truncate_user_account(ctx.db) 280 280 } 281 281 282 - pub fn resolve_occurrence_test() { 282 + pub fn close_occurrence_test() { 283 283 let ctx = app_test.global_data() 284 - let base_path = "/occurrence/resolved_at/" 284 + let base_path = "/occurrence/close/" 285 285 use _ <- list.each(list.range(1, app_test.n_tests)) 286 286 287 287 // DUMMY --------------------------------------------------------------------- ··· 341 341 let assert Ok(_) = dev_sql.soft_truncate_user_account(ctx.db) 342 342 } 343 343 344 - pub fn resolve_missing_occurrence_test() { 344 + pub fn close_missing_occurrence_test() { 345 345 let ctx = app_test.global_data() 346 - let base_path = "/occurrence/resolved_at/" 346 + let base_path = "/occurrence/close/" 347 347 let path = base_path <> uuid.v7_string() 348 348 349 349 let req = ··· 356 356 357 357 pub fn reopen_occurrence_test() { 358 358 let ctx = app_test.global_data() 359 - let base_path = "/occurrence/resolved_at/" 359 + let base_path = "/occurrence/reopen/" 360 360 use _ <- list.each(list.range(1, app_test.n_tests)) 361 361 362 362 // DUMMY --------------------------------------------------------------------- ··· 375 375 ) 376 376 377 377 let path = base_path <> uuid.to_string(dummy_occurrence) 378 - let req = simulate.request(http.Delete, path) 378 + let req = simulate.request(http.Post, path) 379 379 let resp = http_router.handle_request(req, ctx) 380 380 381 381 assert resp.status == 401 as "Restricted to authenticated Users" ··· 414 414 415 415 pub fn reopen_missing_occurrence_test() { 416 416 let ctx = app_test.global_data() 417 - let base_path = "/occurrence/resolved_at/" 417 + let base_path = "/occurrence/reopen/" 418 418 let path = base_path <> uuid.v7_string() 419 419 420 420 let req = 421 - simulate.browser_request(http.Delete, path) 421 + simulate.browser_request(http.Post, path) 422 422 |> app_test.with_authorization() 423 423 424 424 let resp = http_router.handle_request(req, ctx)
+1 -1
test/user_test.gleam
··· 227 227 }) 228 228 as "Response contain invalid JSON" 229 229 230 - let path = "/user/profile" 230 + let path = "/user/profile/update" 231 231 let login_req = 232 232 simulate.browser_request(http.Post, login_path) 233 233 |> simulate.form_body([