this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

:tada: init project

kacaii.dev 52a2e966

+342
+21
.dockerignore
··· 1 + # vcs 2 + .git 3 + .jj 4 + 5 + # secrets 6 + .envrc 7 + .env.example 8 + 9 + # cache 10 + .rumdl_cache/ 11 + 12 + # dev 13 + Containerfile 14 + justfile 15 + .sqruff 16 + 17 + build 18 + dev 19 + test 20 + justfile 21 + priv/static
+8
.env.example
··· 1 + # DATABASE 2 + export POSTGRES_USER="" 3 + export POSTGRES_PASSWORD="" 4 + export POSTGRES_DB="" 5 + 6 + # SERVER 7 + export SECRET_KEY="" 8 + export DATABASE_URL="postgresql://user:password@host:5432/database"
+7
.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump 5 + 6 + priv/static/ 7 + priv/static/
+2
.sqruff
··· 1 + [sqruff] 2 + dialect = postgres
+23
Containerfile
··· 1 + ARG GLEAM_VERSION=v1.15.4 2 + ARG ERLANG_VERSION=28.4.2 3 + 4 + # builder stage ---------------------------------------------------------------- 5 + FROM ghcr.io/gleam-lang/gleam:${GLEAM_VERSION}-scratch as gleam 6 + FROM docker.io/erlang:${ERLANG_VERSION}-alpine as builder 7 + 8 + COPY --from=gleam /bin/gleam /bin/gleam 9 + 10 + COPY ./server /build/server 11 + 12 + WORKDIR /build/server 13 + RUN gleam deps download &&\ 14 + gleam export erlang-shipment 15 + 16 + # runtime stage ---------------------------------------------------------------- 17 + FROM docker.io/erlang:${ERLANG_VERSION}-alpine 18 + COPY --from=builder /build/server/build/erlang-shipment /app 19 + 20 + EXPOSE 8000 21 + WORKDIR /app 22 + ENTRYPOINT ["/app/entrypoint.sh"] 23 + CMD ["run"]
+18
gleam.toml
··· 1 + name = "server" 2 + version = "1.0.0" 3 + 4 + [dependencies] 5 + gleam_stdlib = ">= 0.44.0 and < 2.0.0" 6 + youid = ">= 1.6.0 and < 2.0.0" 7 + wisp = ">= 2.2.2 and < 3.0.0" 8 + mist = ">= 6.0.2 and < 7.0.0" 9 + gleam_erlang = ">= 1.3.0 and < 2.0.0" 10 + pog = ">= 4.1.0 and < 5.0.0" 11 + filepath = ">= 1.1.2 and < 2.0.0" 12 + envoy = ">= 1.1.0 and < 2.0.0" 13 + gleam_otp = ">= 1.2.0 and < 2.0.0" 14 + gleam_crypto = ">= 1.5.2 and < 2.0.0" 15 + 16 + [dev_dependencies] 17 + gleeunit = ">= 1.0.0 and < 2.0.0" 18 + squirrel = ">= 4.6.0 and < 5.0.0"
+68
justfile
··· 1 + # database 2 + 3 + pg_image := "docker.io/postgres:18-alpine" 4 + pg_env := "-e POSTGRES_USER -e POSTGRES_PASSWORD -e POSTGRES_DB" 5 + pg_volume := "-v ./sql/create:/docker-entrypoint-initdb.d" 6 + pg_health_cmd := "--health-cmd 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'" 7 + pg_health_conf := "--health-interval 10s --health-retries 3 --health-timeout 5s" 8 + pg_health_start_period := "--health-start-period 30s" 9 + 10 + # server 11 + 12 + server_name := "senac_backend" 13 + server_port := "-p 8000:8000" 14 + server_health_cmd := "--health-cmd 'wget -q --spider http://127.0.0.1:8000/healthcheck || exit 1'" 15 + server_health_conf := "--health-interval 30s --health-retries 3 --health-timeout 10s" 16 + server_health_start_period := "--health-start-period 10s" 17 + 18 + @_default: 19 + just --list 20 + 21 + #  Generate SQL queries 22 + [group("dev")] 23 + sql: 24 + gleam run -m squirrel 25 + 26 + #  Update project dependencies 27 + [group("dev")] 28 + @_deps-update: 29 + gleam deps update 30 + 31 + # 󰏖 Build the server container image 32 + [group("podman")] 33 + build-server: 34 + podman build -t {{ server_name }} . 35 + 36 + #  Run database container 37 + [group("podman")] 38 + [no-cd] 39 + @init-database pod_name: 40 + podman run -d \ 41 + --pod {{ pod_name }} --name db \ 42 + {{ pg_env }} {{ pg_volume }} \ 43 + {{ pg_health_cmd }} {{ pg_health_conf }} {{ pg_health_start_period }} \ 44 + {{ pg_image }} 45 + 46 + #  Run app container 47 + [group("podman")] 48 + @init-server pod_name: build-server 49 + podman run -d \ 50 + --pod {{ pod_name }} \ 51 + --name server \ 52 + -e DATABASE_URL -e SECRET_KEY \ 53 + {{ server_health_cmd }} \ 54 + {{ server_health_conf }} \ 55 + {{ server_health_start_period }} \ 56 + {{ server_name }} 57 + 58 + [group("dev")] 59 + _test: 60 + gleam test 61 + 62 + [group("dev")] 63 + @_lint: 64 + sqruff lint 65 + gleam check 66 + 67 + @_fmt: 68 + gleam format
+62
manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, 6 + { name = "backoff", version = "1.1.6", build_tools = ["rebar3"], requirements = [], otp_app = "backoff", source = "hex", outer_checksum = "CF0CFFF8995FB20562F822E5CC47D8CCF664C5ECDC26A684CBE85C225F9D7C39" }, 7 + { name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" }, 8 + { name = "envoy", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "850DA9D29D2E5987735872A2B5C81035146D7FE19EFC486129E44440D03FD832" }, 9 + { name = "eval", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "eval", source = "hex", outer_checksum = "264DAF4B49DF807F303CA4A4E4EBC012070429E40BE384C58FE094C4958F9BDA" }, 10 + { name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" }, 11 + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 12 + { name = "glam", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "237C2CE218A2A0A5D46D625F8EF5B78F964BC91018B78D692B17E1AB84295229" }, 13 + { name = "gleam_community_ansi", version = "1.4.4", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "1B3AEA6074AB34D5F0674744F36DDC7290303A03295507E2DEC61EDD6F5777FE" }, 14 + { name = "gleam_community_colour", version = "2.0.4", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "6DB4665555D7D2B27F0EA32EF47E8BEBC4303821765F9C73D483F38EE24894F0" }, 15 + { name = "gleam_crypto", version = "1.5.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "43A25BCE17834475AEA7FFC9408B6D0FC1D709A682BCAE5FEFC6D4192CF47718" }, 16 + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, 17 + { name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" }, 18 + { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, 19 + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, 20 + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, 21 + { name = "gleam_stdlib", version = "0.71.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "702F3BC2A14793906880B1078B19A6165F87323AEE8D0C4A34085846336FCAAE" }, 22 + { name = "gleam_time", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "533D8723774D61AD4998324F5DD1DABDCDBFABAFB9E87CB5D03C6955448FC97D" }, 23 + { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, 24 + { name = "glexer", version = "2.3.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "splitter"], otp_app = "glexer", source = "hex", outer_checksum = "41D8D2E855AEA87ADC94B7AF26A5FEA3C90268D4CF2CCBBD64FD6863714EE085" }, 25 + { name = "glisten", version = "9.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging"], otp_app = "glisten", source = "hex", outer_checksum = "D92808C66F7D3F22F2289CD04CBA8151757AAE9CB3D86992F0C6DE32A41205E1" }, 26 + { name = "gramps", version = "6.0.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "8B7195978FBFD30B43DF791A8A272041B81E45D245314D7A41FC57237AA882A0" }, 27 + { name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" }, 28 + { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, 29 + { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, 30 + { name = "logging", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "BC5F18CE5DD9686100229FE5409BDC3DD5C46D5A7DF2F804AD2D8F0DD6C5060E" }, 31 + { name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" }, 32 + { name = "mist", version = "6.0.2", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "6B03DEEA38A02F276333CB27B53B16D3D45BD741B89599085A601BAF635F2006" }, 33 + { name = "mug", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "mug", source = "hex", outer_checksum = "C01279D98E40371DA23461774B63F0E3581B8F1396049D881B0C7EB32799D93F" }, 34 + { name = "non_empty_list", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "non_empty_list", source = "hex", outer_checksum = "06F9D3AC751CF7853AD5D24B8139CEB30E42D8799DE8D49F966A6197DF0B01CC" }, 35 + { name = "opentelemetry_api", version = "1.5.0", build_tools = ["rebar3", "mix"], requirements = [], otp_app = "opentelemetry_api", source = "hex", outer_checksum = "F53EC8A1337AE4A487D43AC89DA4BD3A3C99DDF576655D071DEED8B56A2D5DDA" }, 36 + { name = "pg_types", version = "0.6.0", build_tools = ["rebar3"], requirements = [], otp_app = "pg_types", source = "hex", outer_checksum = "9949A4849DD13408FA249AB7B745E0D2DFDB9532AEE2B9722326E33CD082A778" }, 37 + { name = "pgo", version = "0.20.0", build_tools = ["rebar3"], requirements = ["backoff", "opentelemetry_api", "pg_types"], otp_app = "pgo", source = "hex", outer_checksum = "2F11E6649CEB38E569EF56B16BE1D04874AE5B11A02867080A2817CE423C683B" }, 38 + { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, 39 + { name = "pog", version = "4.1.0", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_otp", "gleam_stdlib", "gleam_time", "pgo"], otp_app = "pog", source = "hex", outer_checksum = "E4AFBA39A5FAA2E77291836C9683ADE882E65A06AB28CA7D61AE7A3AD61EBBD5" }, 40 + { name = "simplifile", version = "2.4.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "7C18AFA4FED0B4CE1FA5B0B4BAC1FA1744427054EA993565F6F3F82E5453170D" }, 41 + { name = "splitter", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "splitter", source = "hex", outer_checksum = "3DFD6B6C49E61EDAF6F7B27A42054A17CFF6CA2135FF553D0CB61C234D281DD0" }, 42 + { name = "squirrel", version = "4.6.0", build_tools = ["gleam"], requirements = ["argv", "envoy", "eval", "filepath", "glam", "gleam_community_ansi", "gleam_crypto", "gleam_json", "gleam_regexp", "gleam_stdlib", "gleam_time", "glexer", "justin", "mug", "non_empty_list", "pog", "simplifile", "term_size", "tom", "tote", "youid"], otp_app = "squirrel", source = "hex", outer_checksum = "0ED10A868BDD1A5D4B68D99CD1C72DC3F23C6E36E16D33454C5F0C31BAC9CB1E" }, 43 + { name = "term_size", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "term_size", source = "hex", outer_checksum = "D00BD2BC8FB3EBB7E6AE076F3F1FF2AC9D5ED1805F004D0896C784D06C6645F1" }, 44 + { name = "tom", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_time"], otp_app = "tom", source = "hex", outer_checksum = "234A842F3D087D35737483F5DFB6DE9839E3366EF0CAF8726D2D094210227670" }, 45 + { name = "tote", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tote", source = "hex", outer_checksum = "A249892E26A53C668897F8D47845B0007EEE07707A1A03437487F0CD5A452CA5" }, 46 + { name = "wisp", version = "2.2.2", build_tools = ["gleam"], requirements = ["directories", "exception", "filepath", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "5FF5F1E288C3437252ABB93D8F9CF42FF652CE7AD54480CFE736038DC09C4F22" }, 47 + { name = "youid", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_stdlib", "gleam_time"], otp_app = "youid", source = "hex", outer_checksum = "7A3ABA44B1B38BC2BDCB5474C5317AA372BE58DFBC649815EE08B03526DDA18D" }, 48 + ] 49 + 50 + [requirements] 51 + envoy = { version = ">= 1.1.0 and < 2.0.0" } 52 + filepath = { version = ">= 1.1.2 and < 2.0.0" } 53 + gleam_crypto = { version = ">= 1.5.2 and < 2.0.0" } 54 + gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" } 55 + gleam_otp = { version = ">= 1.2.0 and < 2.0.0" } 56 + gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 57 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 58 + mist = { version = ">= 6.0.2 and < 7.0.0" } 59 + pog = { version = ">= 4.1.0 and < 5.0.0" } 60 + squirrel = { version = ">= 4.6.0 and < 5.0.0" } 61 + wisp = { version = ">= 2.2.2 and < 3.0.0" } 62 + youid = { version = ">= 1.6.0 and < 2.0.0" }
+48
src/server.gleam
··· 1 + import envoy 2 + import filepath as path 3 + import gleam/erlang/process 4 + import gleam/result 5 + import pog 6 + import server/context.{Context} 7 + import server/router 8 + import server/supervision_tree 9 + import wisp 10 + 11 + pub fn main() -> Nil { 12 + wisp.configure_logger() 13 + 14 + let db_process_name = process.new_name("db_conn") 15 + let db = pog.named_connection(db_process_name) 16 + 17 + let assert Ok(static_directory) = static_directory() 18 + let assert Ok(secret_key) = envoy.get("SECRET_KEY") 19 + let assert Ok(pog_config) = read_connection_url(db_process_name) 20 + 21 + let ctx = Context(db:, static_directory:, secret_key:) 22 + let handler = router.handle_request(_, ctx) 23 + 24 + let assert Ok(_) = supervision_tree.init(pog_config, handler, secret_key) 25 + 26 + // All done! 27 + process.sleep_forever() 28 + } 29 + 30 + /// Reads the postgres connection string from the environment 31 + /// 32 + /// ## Example 33 + /// 34 + /// ```sh 35 + /// DATABASE_URL="postgresql://user:password@host:port/database" 36 + /// ``` 37 + pub fn read_connection_url( 38 + name: process.Name(pog.Message), 39 + ) -> Result(pog.Config, Nil) { 40 + use config <- result.try(envoy.get("DATABASE_URL")) 41 + pog.url_config(name, config) 42 + } 43 + 44 + /// Path to the application priv directory 45 + pub fn static_directory() -> Result(String, Nil) { 46 + use priv <- result.map(wisp.priv_directory("server")) 47 + path.join(priv, "static") 48 + }
+21
src/server/auth.gleam
··· 1 + import gleam/bit_array 2 + import gleam/crypto 3 + 4 + /// Algoritm used to hash sensitive information 5 + pub const hash_algorithm = crypto.Sha512 6 + 7 + ///  Hash sensitive information 8 + pub fn hash(combine value: String, with salt: String) -> String { 9 + let value = bit_array.from_string(value) 10 + let salt = bit_array.from_string(salt) 11 + 12 + crypto.sign_message(value, salt, hash_algorithm) 13 + } 14 + 15 + ///  Safely compare two values 16 + pub fn compare(one left: String, other right: String) -> Bool { 17 + let left = bit_array.from_string(left) 18 + let right = bit_array.from_string(right) 19 + 20 + crypto.secure_compare(left, right) 21 + }
+12
src/server/context.gleam
··· 1 + import pog 2 + 3 + pub type Context { 4 + Context( 5 + /// Path to the application's priv directory 6 + static_directory: String, 7 + /// PostgresSQL database connection 8 + db: pog.Connection, 9 + /// Secret used for hashing cookies and passwords 10 + secret_key: String, 11 + ) 12 + }
+27
src/server/router.gleam
··· 1 + import server/context.{type Context} 2 + import wisp.{type Request, type Response} 3 + 4 + pub fn handle_request(req: Request, ctx: Context) -> Response { 5 + use req <- middleware(req, ctx) 6 + 7 + case wisp.path_segments(req) { 8 + ["healthcheck"] -> wisp.ok() 9 + 10 + _ -> wisp.not_found() 11 + } 12 + } 13 + 14 + fn middleware( 15 + req: Request, 16 + ctx: Context, 17 + next: fn(Request) -> Response, 18 + ) -> Response { 19 + let req = wisp.method_override(req) 20 + use <- wisp.log_request(req) 21 + use <- wisp.rescue_crashes() 22 + use req <- wisp.handle_head(req) 23 + use req <- wisp.csrf_known_header_protection(req) 24 + use <- wisp.serve_static(req, "/static", ctx.static_directory) 25 + 26 + next(req) 27 + }
+25
src/server/supervision_tree.gleam
··· 1 + import gleam/otp/actor 2 + import gleam/otp/static_supervisor as supervisor 3 + import mist 4 + import pog 5 + import wisp.{type Request, type Response} 6 + import wisp/wisp_mist 7 + 8 + pub fn init( 9 + db_config: pog.Config, 10 + http_handler: fn(Request) -> Response, 11 + secret_key: String, 12 + ) -> Result(actor.Started(supervisor.Supervisor), actor.StartError) { 13 + let handler = wisp_mist.handler(http_handler, secret_key) 14 + 15 + let mist_builder = 16 + handler 17 + |> mist.new() 18 + |> mist.bind("0.0.0.0") 19 + |> mist.port(8000) 20 + 21 + supervisor.new(supervisor.OneForOne) 22 + |> supervisor.add(pog.supervised(db_config)) 23 + |> supervisor.add(mist.supervised(mist_builder)) 24 + |> supervisor.start() 25 + }