diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e48d9e..1c0237e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v1.8.0 - 2024-11-04 + +- Organisation sponsorship question only shows if you say you are in an + organisation. + ## v1.7.0 - 2024-11-04 - Changed "company" to "organisation". diff --git a/gleam.toml b/gleam.toml index a8f088e..f2af067 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,5 +1,5 @@ name = "survey" -version = "1.7.0" +version = "1.8.0" licences = ["Apache-2.0"] # Fill out these fields if you intend to generate HTML documentation or publish diff --git a/src/survey.gleam b/src/survey.gleam index cc7558b..3559bd7 100644 --- a/src/survey.gleam +++ b/src/survey.gleam @@ -1,15 +1,28 @@ +import decode/zero +import gleam/dict import gleam/erlang/process +import gleam/http.{Get, Post} +import gleam/http/request +import gleam/json +import gleam/list +import gleam/pair +import gleam/result +import gleam/set +import gleam/string +import gleam/string_builder import mist -import survey/router -import wisp +import storail +import tempo/datetime +import wisp.{type Request, type Response} import wisp/wisp_mist +import youid/uuid pub fn main() { wisp.configure_logger() let secret_key_base = wisp.random_string(64) let assert Ok(_) = - wisp_mist.handler(router.handle_request, secret_key_base) + wisp_mist.handler(handle_request, secret_key_base) |> mist.new |> mist.port(8000) |> mist.bind("0.0.0.0") @@ -17,3 +30,863 @@ pub fn main() { process.sleep_forever() } + +pub fn handle_request(req: Request) -> Response { + let req = wisp.method_override(req) + use <- wisp.log_request(req) + use <- wisp.rescue_crashes + use req <- wisp.handle_head(req) + + case req.method { + Get -> show_form(req) + Post -> handle_form_submission(req) + _ -> wisp.method_not_allowed(allowed: [Get, Post]) + } +} + +pub fn show_form(req: Request) -> Response { + let html = case wisp.get_cookie(req, cookie_name, wisp.PlainText) { + Ok(_) -> html_thanks + Error(_) -> html_form + } + + wisp.ok() + |> wisp.html_body(string_builder.from_string(html)) +} + +pub fn handle_form_submission(req: Request) -> Response { + use formdata <- wisp.require_form(req) + let names = set.from_list(question_names) + let id = uuid.v7_string() + + let answers = + formdata.values + |> list.filter(fn(pair) { set.contains(names, pair.0) }) + |> list.filter(fn(pair) { pair.1 != "" }) + |> list.group(fn(pair) { pair.0 }) + |> dict.to_list + |> list.map(fn(pair) { + #(pair.0, list.map(pair.1, pair.second) |> string.join(",")) + }) + + let assert Ok(_) = + data_collection() + |> storail.key(id) + |> storail.write([ + #("id", id), + #("ip", request.get_header(req, "cf-connecting-ip") |> result.unwrap("")), + #("inserted-at", datetime.now_utc() |> datetime.to_string), + ..answers + ]) + + wisp.ok() + |> wisp.set_cookie(req, cookie_name, ":)", wisp.PlainText, 60 * 60 * 24 * 90) + |> wisp.html_body(string_builder.from_string(html_thanks)) +} + +fn data_collection() { + let config = + storail.Config(data_directory: "data", temporary_directory: "data/tmp") + storail.Collection( + name: "submission", + config:, + to_json: fn(data) { + list.map(data, pair.map_second(_, json.string)) |> json.object + }, + decoder: zero.dict(zero.string, zero.string) |> zero.map(dict.to_list), + ) +} + +const question_names = [ + "projects", "individual-sponsor", "organisation-sponsor", "sponsor-motivation", + "gleam-user", "gleam-experience", "gleam-open-source", "targets", + "writing-libraries", "writing-applications", "runtimes", "gleam-in-production", + "organisation-name", "professional-experience", "other-languages", + "news-sources", "country", "likes", "improvements", "job-role", + "organisation-size", "production-os", "development-os", "anything-else", +] + +const html_head = " + + + + + + + Developer Survey 2024 + + + + +

Gleam Developer Survey 2024

+" + +const html_foot = " + + + +" + +const html_form = html_head + <> " +

+ All questions are optional, so fill in as much or as little as you like. If + you have any question or problems please share them in + the Gleam Discord server. +

+

+ Do share this survey with your Gleamy friends! +

+ +
+
+ What country do you live in? + +
+ +
+ What's your job role? + +
+ +
+ How many years of professional programming experience do you have? + +
+ +
+ How large is your organisation? + +
+ +
+ What other languages do you use? + + +
+ +
+ What operating systems do you use in development? +
+ + + + + + + + +
+
+ + +
+
+ +
+ What operating systems do you use in production? +
+ + + + + + + + +
+
+ + +
+
+ +
+ Have you used Gleam? + + +
+ +
+
+ How many years of Gleam programming experience do you have? + +
+ +
+ What Gleam compilation targets do you use? + + + +
+ +
+ What do you write in Gleam? + + +
+ +
+ What runtimes do you use? +
+ + + + + + + + +
+
+ + +
+
+ +
+ Do you use Gleam for open source? + + +
+ +
+ Do you use Gleam in production? + + +
+ +
+ What is your organisation's name? + +
+
+ +
+ Where do you get your Gleam news? + + +
+ +
+ Do you sponsor Gleam? + + +

+ Gleam has no corporate owner or other funding source. I rely on the the + kind support of Gleam's sponsors to pay my bills and to support language + development. +

+

+ Thank you so much! 💜 +

+
+ +
+ Does your organisation sponsor Gleam? + + +

+ Thank you so much! 💜 +

+
+ +
+ What might make you consider sponsoring? + +
+ +
+ What do you like about Gleam? + +
+ +
+ What would you like see the Gleam team work on in 2025? + +
+ +
+ Anything else you'd like to say? + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +" + <> html_foot + +const html_thanks = html_head + <> " +

+ Thank you! The results will be shared before the end of the year on the Gleam website. +

+

+ Please share this survey with your Gleamy friends. +

+" + <> html_foot + +const cookie_name = "gleam-developer-survey-submitted" + +const css = " +@font-face { + font-family: 'Lexend'; + font-display: swap; + font-weight: 400; + src: url('https://gleam.run/fonts/Lexend.woff2') format('woff2'); +} + +@font-face { + font-family: 'Lexend'; + font-display: swap; + font-weight: 700; + src: url('https://gleam.run/fonts/Lexend-700.woff2') format('woff2'); +} + +@font-face { + font-family: 'Outfit'; + font-display: swap; + src: url('https://gleam.run/fonts/Outfit.woff') format('woff'); +} + +:root { + --font-family-normal: 'Outfit', sans-serif; + --font-family-title: 'Lexend', sans-serif; + --color-underwater-blue: #292d3e; + --color-white: #fefefc; + --color-faff-pink: #ffaff3; + --width-content: 640px; + --font-size-normal: 18px; + --gap-1: 10px; + --gap-2: calc(var(--gap-1) * 2); + --gap-3: calc(var(--gap-1) * 3); + --gap-4: calc(var(--gap-1) * 4); +} + +*, *::before, *::after { + box-sizing: border-box; +} + +* { + margin: 0; +} + +body { + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +img, picture, video, canvas, svg { + display: block; + max-width: 100%; +} + +input, button, textarea, select { + font: inherit; +} + +p, h1, h2, h3, h4, h5, h6 { + overflow-wrap: break-word; +} + +p { + text-wrap: pretty; + margin: 8 0; +} +h1, h2, h3, h4, h5, h6 { + text-wrap: balance; +} + +#root, #__next { + isolation: isolate; +} + +[data-show-if] { + display: none; +} + +body { + background-color: var(--color-underwater-blue); + color: var(--color-white); + font-family: var(--font-family-normal); + font-size: var(--font-size-normal); + max-width: 100%; + width: var(--width-content); + margin: 0 auto; + padding: var(--gap-2); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--font-family-title); + font-weight: var(--font-weight-normal); +} + +a:visited, +a { + color: var(--color-white); + text-decoration-color: var(--color-faff-pink); +} + +fieldset { + padding: 0; + border: 0; + margin: var(--gap-2) 0; +} + +select, +textarea, +input:not([type='checkbox']):not([type='radio']) { + display: block; + width: 100%; + margin: var(--gap-1) 0; + padding: 4px var(--gap-1); + border-radius: 1px; + border: none; +} + +select { + appearance: none; +} + +input[type='checkbox'], +input[type='radio'] { + margin-right: var(--gap-1); +} + +label { + display: block; +} + +legend { + font-size: 110%; +} + +.columns { + column-count: 2; +} + +h1 { + margin: var(--gap-2) 0; +} + +form { + margin: var(--gap-4) 0; +} + +footer { + margin-top: var(--gap-4); + text-align: center; + font-size: 80%; +} + +img { + margin: 0 auto; + max-width: 200px; +} +" diff --git a/src/survey/router.gleam b/src/survey/router.gleam deleted file mode 100644 index 5149ee3..0000000 --- a/src/survey/router.gleam +++ /dev/null @@ -1,877 +0,0 @@ -import decode/zero -import gleam/dict -import gleam/http.{Get, Post} -import gleam/http/request -import gleam/json -import gleam/list -import gleam/pair -import gleam/result -import gleam/set -import gleam/string -import gleam/string_builder -import storail -import tempo/datetime -import wisp.{type Request, type Response} -import youid/uuid - -pub fn handle_request(req: Request) -> Response { - let req = wisp.method_override(req) - use <- wisp.log_request(req) - use <- wisp.rescue_crashes - use req <- wisp.handle_head(req) - - case req.method { - Get -> show_form(req) - Post -> handle_form_submission(req) - _ -> wisp.method_not_allowed(allowed: [Get, Post]) - } -} - -pub fn show_form(req: Request) -> Response { - let html = case wisp.get_cookie(req, cookie_name, wisp.PlainText) { - Ok(_) -> html_thanks - Error(_) -> html_form - } - - wisp.ok() - |> wisp.html_body(string_builder.from_string(html)) -} - -pub fn handle_form_submission(req: Request) -> Response { - use formdata <- wisp.require_form(req) - let names = set.from_list(question_names) - let id = uuid.v7_string() - - let answers = - formdata.values - |> list.filter(fn(pair) { set.contains(names, pair.0) }) - |> list.filter(fn(pair) { pair.1 != "" }) - |> list.group(fn(pair) { pair.0 }) - |> dict.to_list - |> list.map(fn(pair) { - #(pair.0, list.map(pair.1, pair.second) |> string.join(",")) - }) - - let assert Ok(_) = - data_collection() - |> storail.key(id) - |> storail.write([ - #("id", id), - #("ip", request.get_header(req, "cf-connecting-ip") |> result.unwrap("")), - #("inserted-at", datetime.now_utc() |> datetime.to_string), - ..answers - ]) - - wisp.ok() - |> wisp.set_cookie(req, cookie_name, ":)", wisp.PlainText, 60 * 60 * 24 * 90) - |> wisp.html_body(string_builder.from_string(html_thanks)) -} - -fn data_collection() { - let config = - storail.Config(data_directory: "data", temporary_directory: "data/tmp") - storail.Collection( - name: "submission", - config:, - to_json: fn(data) { - list.map(data, pair.map_second(_, json.string)) |> json.object - }, - decoder: zero.dict(zero.string, zero.string) |> zero.map(dict.to_list), - ) -} - -const question_names = [ - "projects", "individual-sponsor", "organisation-sponsor", "sponsor-motivation", - "gleam-user", "gleam-experience", "gleam-open-source", "targets", - "writing-libraries", "writing-applications", "runtimes", "gleam-in-production", - "organisation-name", "professional-experience", "other-languages", - "news-sources", "country", "likes", "improvements", "job-role", - "organisation-size", "production-os", "development-os", "anything-else", -] - -const html_head = " - - - - - - - Developer Survey 2024 - - - - -

Gleam Developer Survey 2024

-" - -const html_foot = " - - - -" - -const html_form = html_head - <> " -

- All questions are optional, so fill in as much or as little as you like. If - you have any question or problems please share them in - the Gleam Discord server. -

-

- Do share this survey with your Gleamy friends! -

- -
-
- What country do you live in? - -
- -
- What's your job role? - -
- -
- How many years of professional programming experience do you have? - -
- -
- How large is your organisation? - -
- -
- What other languages do you use? - - -
- -
- What operating systems do you use in development? -
- - - - - - - - -
-
- - -
-
- -
- What operating systems do you use in production? -
- - - - - - - - -
-
- - -
-
- -
- Have you used Gleam? - - -
- -
-
- How many years of Gleam programming experience do you have? - -
- -
- What Gleam compilation targets do you use? - - - -
- -
- What do you write in Gleam? - - -
- -
- What runtimes do you use? -
- - - - - - - - -
-
- - -
-
- -
- Do you use Gleam for open source? - - -
- -
- Do you use Gleam in production? - - -
- -
-
- What is your organisation's name? - -
-
-
- -
- Where do you get your Gleam news? - - -
- -
- Do you sponsor Gleam? - - -

- Gleam has no corporate owner or other funding source. I rely on the the - kind support of Gleam's sponsors to pay my bills and to support language - development. -

-

- Thank you so much! 💜 -

-
- -
- Does your organisation sponsor Gleam? - - -

- Thank you so much! 💜 -

-
- -
- What might make you consider sponsoring? - -
- -
- What do you like about Gleam? - -
- -
- What would you like see the Gleam team work on in 2025? - -
- -
- Anything else you'd like to say? - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -" - <> html_foot - -const html_thanks = html_head - <> " -

- Thank you! The results will be shared before the end of the year on the Gleam website. -

-

- Please share this survey with your Gleamy friends. -

-" - <> html_foot - -const cookie_name = "gleam-developer-survey-submitted" - -const css = " -@font-face { - font-family: 'Lexend'; - font-display: swap; - font-weight: 400; - src: url('https://gleam.run/fonts/Lexend.woff2') format('woff2'); -} - -@font-face { - font-family: 'Lexend'; - font-display: swap; - font-weight: 700; - src: url('https://gleam.run/fonts/Lexend-700.woff2') format('woff2'); -} - -@font-face { - font-family: 'Outfit'; - font-display: swap; - src: url('https://gleam.run/fonts/Outfit.woff') format('woff'); -} - -:root { - --font-family-normal: 'Outfit', sans-serif; - --font-family-title: 'Lexend', sans-serif; - --color-underwater-blue: #292d3e; - --color-white: #fefefc; - --color-faff-pink: #ffaff3; - --width-content: 640px; - --font-size-normal: 18px; - --gap-1: 10px; - --gap-2: calc(var(--gap-1) * 2); - --gap-3: calc(var(--gap-1) * 3); - --gap-4: calc(var(--gap-1) * 4); -} - -*, *::before, *::after { - box-sizing: border-box; -} - -* { - margin: 0; -} - -body { - line-height: 1.5; - -webkit-font-smoothing: antialiased; -} - -img, picture, video, canvas, svg { - display: block; - max-width: 100%; -} - -input, button, textarea, select { - font: inherit; -} - -p, h1, h2, h3, h4, h5, h6 { - overflow-wrap: break-word; -} - -p { - text-wrap: pretty; - margin: 8 0; -} -h1, h2, h3, h4, h5, h6 { - text-wrap: balance; -} - -#root, #__next { - isolation: isolate; -} - -[data-show-if] { - display: none; -} - -body { - background-color: var(--color-underwater-blue); - color: var(--color-white); - font-family: var(--font-family-normal); - font-size: var(--font-size-normal); - max-width: 100%; - width: var(--width-content); - margin: 0 auto; - padding: var(--gap-2); -} - -h1, -h2, -h3, -h4, -h5, -h6 { - font-family: var(--font-family-title); - font-weight: var(--font-weight-normal); -} - -a:visited, -a { - color: var(--color-white); - text-decoration-color: var(--color-faff-pink); -} - -fieldset { - padding: 0; - border: 0; - margin: var(--gap-2) 0; -} - -select, -textarea, -input:not([type='checkbox']):not([type='radio']) { - display: block; - width: 100%; - margin: var(--gap-1) 0; - padding: 4px var(--gap-1); - border-radius: 1px; - border: none; -} - -select { - appearance: none; -} - -input[type='checkbox'], -input[type='radio'] { - margin-right: var(--gap-1); -} - -label { - display: block; -} - -legend { - font-size: 110%; -} - -.columns { - column-count: 2; -} - -h1 { - margin: var(--gap-2) 0; -} - -form { - margin: var(--gap-4) 0; -} - -footer { - margin-top: var(--gap-4); - text-align: center; - font-size: 80%; -} - -img { - margin: 0 auto; - max-width: 200px; -} -"