From cf7b7b7b33b97f5c6b7841c17c6f9504012882bc Mon Sep 17 00:00:00 2001 From: Avris Date: Fri, 18 Dec 2020 11:34:58 +0100 Subject: [PATCH] =?UTF-8?q?#134=20[pl][census]=20polski=20cenzus=20p=C5=82?= =?UTF-8?q?ci?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/style.scss | 2 +- components/Header.vue | 9 ++ locale/pl/config.suml | 50 ++++++++++ locale/pl/translations.suml | 30 ++++++ migrations/011-census.sql | 15 +++ nuxt.config.js | 4 + routes/census.vue | 191 ++++++++++++++++++++++++++++++++++++ server/index.js | 1 + server/routes/census.js | 64 ++++++++++++ src/helpers.js | 8 ++ src/translator.js | 4 +- 11 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 migrations/011-census.sql create mode 100644 routes/census.vue create mode 100644 server/routes/census.js diff --git a/assets/style.scss b/assets/style.scss index 9b95ff96..9bda52cf 100644 --- a/assets/style.scss +++ b/assets/style.scss @@ -24,7 +24,7 @@ @import "~bootstrap/scss/badge"; //@import "~bootstrap/scss/jumbotron"; @import "~bootstrap/scss/alert"; -//@import "~bootstrap/scss/progress"; +@import "~bootstrap/scss/progress"; //@import "~bootstrap/scss/media"; @import "~bootstrap/scss/list-group"; //@import "~bootstrap/scss/close"; diff --git a/components/Header.vue b/components/Header.vue index 6ab2cd77..bebad204 100644 --- a/components/Header.vue +++ b/components/Header.vue @@ -128,6 +128,15 @@ }); } + if (this.config.census.enabled) { + links.push({ + link: '/' + this.config.census.route, + icon: 'user-chart', + text: this.$t('census.header'), + textLong: this.$t('census.headerLong'), + }); + } + if (this.config.contact.enabled) { links.push({ link: '/' + this.config.contact.route, diff --git a/locale/pl/config.suml b/locale/pl/config.suml index e7da7f94..58b630df 100644 --- a/locale/pl/config.suml +++ b/locale/pl/config.suml @@ -426,6 +426,56 @@ profile: flags: defaultPronoun: 'on_' +census: + enabled: false + route: 'cenzus' + open: true + edition: '2021' + start: '2021-03-01' + end: '2021-03-31' + questions: + - + type: 'radio' + question: 'Gdzie obecnie mieszkasz?' + options: + - 'w Polsce' + - 'za granicą' + - + type: 'number' + min: 12 + max: 120 + question: 'Ile masz lat?' + - + type: 'checkbox' + question: 'Jakimi słowami opisujesz swoją płeć?' + randomise: true + writein: true + options: + - 'niebinarn_' + - 'agender' + - 'agenderow_' + - 'bigender' + - 'bigenderow_' + - 'enby' + - 'niebinie' + - 'queer' + - + type: 'checkbox' + question: 'Jakich zaimków i innych form nacechowanych płciowo używasz?' + randomise: true + writein: true + options: + - 'on/jego' + - 'ona/jej' + - 'ono/jego' + - 'ono/jej' + - 'onu/jenu' + - 'maskulatywy' + - 'feminatywy' + - 'neutratywy' + - 'dukatywy' + - 'osobatywy' + redirects: - { from: '^/neutratywy', to: '/s%C5%82ownik' } - { from: '^/rzeczowniki', to: '/s%C5%82ownik' } diff --git a/locale/pl/translations.suml b/locale/pl/translations.suml index 9d09a8df..45bc04ea 100644 --- a/locale/pl/translations.suml +++ b/locale/pl/translations.suml @@ -886,6 +886,36 @@ profile: meh: 'Spoko' no: 'Nie' +census: + header: 'Cenzus' + headerLong: 'Cenzus płci' + description: + - > + Ciekawi Cię, jakiego języka używają polskie osoby niebinarne? + Które zaimki, rodzaje gramatyczne i inne formy są najpopularniejsze? + Jakimi etykietkami się opisujemy? + Jak te trendy zmieniają się w czasie? + - > + To super, bo nas też! Dlatego chcemy co roku przeprowadzać cenzus, w którym to zbadamy. + Serdecznie zapraszamy do udziału wszystkie osoby niebinarne posługujące się językiem polskim. + - > + Ankieta składa się z %questions% pytań i jest otwarta od %start% do %end%. + W pytaniach wielokrotnego wyboru można zaznaczyć wiele odpowiedzi, jak również dopisać własne. + By uniknąć tendencyjności, kolejność propozycji jest losowa. + agree: > + Wyrażam zgodę na przetwarzanie moich odpowiedzi + oraz na użycie zanonimizowanej wersji mojego adresu IP oraz fingerprintu przeglądarki + do przeciwdziałania wandalizmom i spamowi. + Dane te nie są w stanie zidentyfikować żadnej osoby, + zostaną użyte wyłącznie w celu zapewnienia unikalności odpowiedzi + i usunięte po zamknięciu cenzusu. + start: 'Rozpocznij ankietę' + finished: > + Dziękujemy za wzięcie udziału w cenzusie! + Wyniki i wnioski ogłosimy wkrótce {/kontakt=w mediach społecznościowych}. + prev: 'Poprzednie pytanie' + next: 'Następne pytanie' + share: 'Udostępnij' crud: diff --git a/migrations/011-census.sql b/migrations/011-census.sql new file mode 100644 index 00000000..247b6e81 --- /dev/null +++ b/migrations/011-census.sql @@ -0,0 +1,15 @@ +-- Up + +CREATE TABLE census ( + id TEXT NOT NULL PRIMARY KEY, + locale TEXT NOT NULL, + edition TEXT NOT NULL, + userId TEXT NULL, + fingerprint TEXT NULL, + answers TEXT NOT NULL, + writins TEXT NOT NULL +); + +-- Down + +DROP TABLE census; diff --git a/nuxt.config.js b/nuxt.config.js index b37441c9..855c373e 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -175,6 +175,10 @@ export default { routes.push({ path: '/' + config.contact.route, component: resolve(__dirname, 'routes/contact.vue') }); } + if (config.census.enabled) { + routes.push({ path: '/' + config.census.route, component: resolve(__dirname, 'routes/census.vue') }); + } + if (config.user.enabled) { routes.push({path: '/' + config.user.route, component: resolve(__dirname, 'routes/user.vue')}); routes.push({path: '/' + config.user.termsRoute, component: resolve(__dirname, 'routes/terms.vue')}); diff --git a/routes/census.vue b/routes/census.vue new file mode 100644 index 00000000..33a46615 --- /dev/null +++ b/routes/census.vue @@ -0,0 +1,191 @@ + + + diff --git a/server/index.js b/server/index.js index 617277a2..52f40d7c 100644 --- a/server/index.js +++ b/server/index.js @@ -45,6 +45,7 @@ app.use(require('./routes/sources').default); app.use(require('./routes/nouns').default); app.use(require('./routes/inclusive').default); app.use(require('./routes/pronounce').default); +app.use(require('./routes/census').default); export default { path: '/api', diff --git a/server/routes/census.js b/server/routes/census.js new file mode 100644 index 00000000..053f5ca3 --- /dev/null +++ b/server/routes/census.js @@ -0,0 +1,64 @@ +import { Router } from 'express'; +import SQL from 'sql-template-strings'; +import sha1 from 'sha1'; +import {ulid} from "ulid"; + +const buildFingerprint = req => sha1(` + ${req.ip} + ${req.headers['user-agent']} + ${req.headers['accept-language']} +`); + +const hasFinished = async req => { + if (req.user) { + const byUser = await req.db.get(SQL` + SELECT * FROM census + WHERE locale = ${req.config.locale} + AND edition = ${req.config.census.edition} + AND userId = ${req.user.id} + `); + if (byUser) { + return true; + } + } + + const fingerprint = buildFingerprint(req); + const byFingerprint = await req.db.get(SQL` + SELECT * FROM census + WHERE locale = ${req.config.locale} + AND edition = ${req.config.census.edition} + AND fingerprint = ${fingerprint} + `); + if (byFingerprint) { + return true; + } + + return false; +} + +const router = Router(); + +router.get('/census/finished', async (req, res) => { + return res.json(await hasFinished(req)); +}); + +router.post('/census/submit', async (req, res) => { + if (await hasFinished(req)) { + return res.status(401).json({error: 'Unauthorised'}); + } + + const id = ulid(); + await req.db.get(SQL`INSERT INTO census (id, locale, edition, userId, fingerprint, answers, writins) VALUES ( + ${id}, + ${req.config.locale}, + ${req.config.census.edition}, + ${req.user ? req.user.id : null}, + ${buildFingerprint(req)}, + ${req.body.answers}, + ${req.body.writins} + )`); + + return res.json(id); +}); + +export default router; diff --git a/src/helpers.js b/src/helpers.js index 7ab69637..82f38220 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -163,3 +163,11 @@ export const sortByValue = (obj, reverse = false) => { } return zip(sortedArray.sort((a, b) => reverse ? b[0] - a[0] : a[0] - b[0]), true); } + +export const shuffle = a => { + for (let i = a.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [a[i], a[j]] = [a[j], a[i]]; + } + return a; +} diff --git a/src/translator.js b/src/translator.js index 95949cd6..30491117 100644 --- a/src/translator.js +++ b/src/translator.js @@ -14,7 +14,9 @@ export default (key, params = {}, warn = true) => { for (let k in params) { if (params.hasOwnProperty(k)) { - value = value.replace(new RegExp('%' + k + '%', 'g'), params[k]) + value = Array.isArray(value) + ? value.map(v => v.replace(new RegExp('%' + k + '%', 'g'), params[k])) + : value.replace(new RegExp('%' + k + '%', 'g'), params[k]); } }