From 1fa9378b09510d1d563bcca126aca0593804aae8 Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Sat, 7 Oct 2023 14:56:47 +0200 Subject: [PATCH 01/15] remove unused configuration vars & minor formatting fixes --- .env.example | 2 +- Readme.md | 6 +++--- client/src/components/Authentication.tsx | 2 +- config/config.go | 12 ------------ 4 files changed, 5 insertions(+), 17 deletions(-) diff --git a/.env.example b/.env.example index dbb4405..71ade86 100644 --- a/.env.example +++ b/.env.example @@ -4,4 +4,4 @@ LOGIN_URL=https://login.datasektionen.se LOGIN_KEY=asdfasdfasdfsadfsadfasdf PLS_URL=https://pls.datasektionen.se -DB_URL=postgresql://:@localhost:5432/durn \ No newline at end of file +DATABASE_URL=postgresql://:@localhost:5432/durn diff --git a/Readme.md b/Readme.md index 86abbda..54d3015 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,6 @@ # dUrn - the voting system -This is a implementation of a voting system for the Computer Science Chapter, with a backend written in golang and a frontend written in Typescript React. +This is a implementation of a voting system for the Computer Science Chapter, with a backend written in golang and a frontend written in Typescript React. @@ -25,7 +25,7 @@ The system uses the following permissions in pls: | `LOGIN_URL` | `https://login.datasektionen.se` | url for the login system and API | | `LOGIN_KEY` | | API-key for the login system | | `PLS_URL` | `https://pls.datasektionen.se` | url for the permissions system pls | -| `DATABASE_URL` | | postgres-url for connecting to the database instance | +| `DATABASE_URL` | | postgres-url for connecting to the database instance | ## How to run @@ -44,4 +44,4 @@ The system uses the following permissions in pls: 2. Setup environment variables 3. Run `make prod` in root -The system also has a `dockerfile` setup for deployment. \ No newline at end of file +The system also has a `dockerfile` setup for deployment. diff --git a/client/src/components/Authentication.tsx b/client/src/components/Authentication.tsx index 7b481cc..d09625e 100644 --- a/client/src/components/Authentication.tsx +++ b/client/src/components/Authentication.tsx @@ -83,4 +83,4 @@ export const Token: React.FC = () => { }, [token]) return <>; -} \ No newline at end of file +} diff --git a/config/config.go b/config/config.go index 2e995a2..8bb2326 100644 --- a/config/config.go +++ b/config/config.go @@ -17,12 +17,6 @@ type Config struct { PLS_URL string DATABASE_URL string - - DB_HOST string - DB_PORT int - DB_NAME string - DB_USER string - DB_PSWD string } var ( @@ -66,12 +60,6 @@ func GetConfig() *Config { PLS_URL: loadStringEnv("PLS_URL", "https://pls.datasektionen.se"), DATABASE_URL: loadStringEnv("DATABASE_URL", ""), - - DB_HOST: loadStringEnv("DB_HOST", ""), - DB_PORT: loadIntEnv("DB_PORT", 5432), - DB_NAME: loadStringEnv("DB_NAME", "durn"), - DB_USER: loadStringEnv("DB_USER", "durn"), - DB_PSWD: loadStringEnv("DB_PSWD", ""), } loaded = true From 87b340088984f19e5c8c3e2ef9914422fe4f4646 Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Sat, 7 Oct 2023 14:59:14 +0200 Subject: [PATCH 02/15] Make the name of the system in pls configurable --- Readme.md | 1 + config/config.go | 22 ++++++++++++---------- server/middleware/auth.go | 3 ++- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Readme.md b/Readme.md index 54d3015..4035bc5 100644 --- a/Readme.md +++ b/Readme.md @@ -25,6 +25,7 @@ The system uses the following permissions in pls: | `LOGIN_URL` | `https://login.datasektionen.se` | url for the login system and API | | `LOGIN_KEY` | | API-key for the login system | | `PLS_URL` | `https://pls.datasektionen.se` | url for the permissions system pls | +| `PLS_SYSTEM_NAME` | `durn` | the system to check for permissions in pls | | `DATABASE_URL` | | postgres-url for connecting to the database instance | diff --git a/config/config.go b/config/config.go index 8bb2326..c5a89f8 100644 --- a/config/config.go +++ b/config/config.go @@ -10,11 +10,12 @@ import ( ) type Config struct { - HOST string - PORT int - LOGIN_URL string - LOGIN_KEY string - PLS_URL string + HOST string + PORT int + LOGIN_URL string + LOGIN_KEY string + PLS_URL string + PLS_SYSTEM_NAME string DATABASE_URL string } @@ -53,11 +54,12 @@ func GetConfig() *Config { } conf = Config{ - HOST: loadStringEnv("HOST", "https://localhost.datasektionen.se"), - PORT: loadIntEnv("PORT", 3000), - LOGIN_URL: loadStringEnv("LOGIN_URL", "https://login.datasektionen.se"), - LOGIN_KEY: loadStringEnv("LOGIN_KEY", ""), - PLS_URL: loadStringEnv("PLS_URL", "https://pls.datasektionen.se"), + HOST: loadStringEnv("HOST", "https://localhost.datasektionen.se"), + PORT: loadIntEnv("PORT", 3000), + LOGIN_URL: loadStringEnv("LOGIN_URL", "https://login.datasektionen.se"), + LOGIN_KEY: loadStringEnv("LOGIN_KEY", ""), + PLS_URL: loadStringEnv("PLS_URL", "https://pls.datasektionen.se"), + PLS_SYSTEM_NAME: loadStringEnv("PLS_SYSTEM_NAME", "durn"), DATABASE_URL: loadStringEnv("DATABASE_URL", ""), } diff --git a/server/middleware/auth.go b/server/middleware/auth.go index 4c91575..b2ab59c 100644 --- a/server/middleware/auth.go +++ b/server/middleware/auth.go @@ -58,6 +58,7 @@ func Authenticate() gin.HandlerFunc { func Authorize() gin.HandlerFunc { conf := config.GetConfig() url := conf.PLS_URL + systemName := conf.PLS_SYSTEM_NAME if check, err := http.Get(url + "/"); err != nil || check.StatusCode != 200 { fmt.Println(err) @@ -66,7 +67,7 @@ func Authorize() gin.HandlerFunc { return func(c *gin.Context) { user := c.GetString("userid") - requestURL := fmt.Sprintf("%s/api/user/%s/durn", url, user) + requestURL := fmt.Sprintf("%s/api/user/%s/%s", url, user, systemName) var response []string From 32486d24c35f5cddf7d14a7f753d7129b10446d0 Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Sat, 7 Oct 2023 15:10:06 +0200 Subject: [PATCH 03/15] Add deployment workflow --- .github/workflows/deploy-smurn.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/deploy-smurn.yml diff --git a/.github/workflows/deploy-smurn.yml b/.github/workflows/deploy-smurn.yml new file mode 100644 index 0000000..104eaba --- /dev/null +++ b/.github/workflows/deploy-smurn.yml @@ -0,0 +1,28 @@ +name: Deploy SMurn + +on: + push: + branches: [ master ] + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Git checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # See the following link for documentation: + # https://github.com/marketplace/actions/dokku + - name: Push to medusa + uses: dokku/github-action@v1.4.0 + with: + ssh_private_key: ${{ secrets.MEDUSA_GLOBAL_DEPLOY_KEY }} + git_remote_url: ssh://dokku@medusa.datasektionen.se/smurn + # force might feel risky, but there is no good reason why the server + # should ever not be a mirror of the deploy branch. And the errors we + # could get otherwise would probably be nasty to deal with + git_push_flags: --force From 430916d1dff29d4ac4b2d5a9e222406773b24dfe Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Sat, 7 Oct 2023 16:31:54 +0200 Subject: [PATCH 04/15] Remove time fields from elections --- client/src/components/AdminElectionView.tsx | 49 +-------------------- client/src/components/Voting.tsx | 10 ++--- server/util/misc.go | 2 +- 3 files changed, 8 insertions(+), 53 deletions(-) diff --git a/client/src/components/AdminElectionView.tsx b/client/src/components/AdminElectionView.tsx index 71e23b1..3c20c47 100644 --- a/client/src/components/AdminElectionView.tsx +++ b/client/src/components/AdminElectionView.tsx @@ -76,52 +76,7 @@ export const AdminElectionView: React.FC = ({
- -
- - Election starts - - -
- -
- - Election ends - - -
- -
- - Mandates - - -
- -
- - Secondary mandates - - - -
-
- - - - + = ({ ; -} \ No newline at end of file +} diff --git a/client/src/components/Voting.tsx b/client/src/components/Voting.tsx index 0c4336b..ee9d4f0 100644 --- a/client/src/components/Voting.tsx +++ b/client/src/components/Voting.tsx @@ -168,11 +168,11 @@ export const Voting: React.FC = ({ }] = useDisclosure(false); const hasOpened = useMemo(() => ( - (election.openTime ? dayjs(Date.now()).isAfter(election.openTime) : false) + (election.openTime ? dayjs(Date.now()).isAfter(election.openTime) : true) ), [election.openTime]); const hasClosed = useMemo(() => ( - (election.closeTime ? dayjs(Date.now()).isAfter(election.closeTime) : true) + (election.closeTime ? dayjs(Date.now()).isAfter(election.closeTime) : false) ), [election.closeTime]); const disabled = useMemo(() => ( @@ -396,12 +396,12 @@ const DraggableCandidate: React.FC = ({ {candidate.presentation != "" && !candidate.symbolic ? -
- Presentation +
+ {candidate.presentation}
: <>}
}} -} \ No newline at end of file +} diff --git a/server/util/misc.go b/server/util/misc.go index e5c8ad1..09a1d79 100644 --- a/server/util/misc.go +++ b/server/util/misc.go @@ -16,7 +16,7 @@ func ValidEmail(email string) bool { // both a start and an end. func TimeIsInValidInterval(time time.Time, start sql.NullTime, end sql.NullTime) bool { if !start.Valid || !end.Valid { - return false + return true } if time.Before(start.Time) || time.After(end.Time) { return false From 97935b25e9020f15c12ec13e1df2d8375f17b70f Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Sat, 7 Oct 2023 16:33:24 +0200 Subject: [PATCH 05/15] Rename presentation to description --- client/src/components/CandidateList.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/components/CandidateList.tsx b/client/src/components/CandidateList.tsx index 7809bd0..8816477 100644 --- a/client/src/components/CandidateList.tsx +++ b/client/src/components/CandidateList.tsx @@ -71,7 +71,7 @@ export const CandidateList: React.FC = ( setPresentation(element.target.value)} /> @@ -113,7 +113,7 @@ export const CandidateList: React.FC = ( - Candidate presentation + Description @@ -183,4 +183,4 @@ const CandidateRow: React.FC = ( -}; \ No newline at end of file +}; From ccadd572bb15e9b96d69e98b4ef816d3bc2f91bd Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Sat, 7 Oct 2023 16:36:57 +0200 Subject: [PATCH 06/15] Make Vacancy not implicit --- client/src/views/admin/EditElection.tsx | 4 ++-- server/actions/elections.go | 26 +++++++++++-------------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/client/src/views/admin/EditElection.tsx b/client/src/views/admin/EditElection.tsx index ab47e17..53b9b0e 100644 --- a/client/src/views/admin/EditElection.tsx +++ b/client/src/views/admin/EditElection.tsx @@ -75,7 +75,7 @@ const EditElection: React.FC = () => { closeTime: electionData.closeTime, }); candidatesHandler.setState( - electionData.candidates.filter((c: Candidate) => !c.symbolic) + electionData.candidates .map((c: Candidate) => ({ ...c, changed: false, @@ -349,4 +349,4 @@ const EditElection: React.FC = () => { } -export default EditElection; \ No newline at end of file +export default EditElection; diff --git a/server/actions/elections.go b/server/actions/elections.go index 8f940d0..e6dcc06 100644 --- a/server/actions/elections.go +++ b/server/actions/elections.go @@ -82,21 +82,21 @@ func CreateElection(c *gin.Context) { Published: false, Finalized: false, } - vacant := database.Candidate{ - ID: uuid.NewV4(), - Name: util.VacantCandidate, - Presentation: "", - ElectionID: election.ID, - Symbolic: true, - } + // vacant := database.Candidate{ + // ID: uuid.NewV4(), + // Name: util.VacantCandidate, + // Presentation: "", + // ElectionID: election.ID, + // Symbolic: true, + // } if err := db.Transaction(func(tx *gorm.DB) error { if err := db.Create(&election).Error; err != nil { return err } - if err := db.Create(&vacant).Error; err != nil { - return err - } + // if err := db.Create(&vacant).Error; err != nil { + // return err + // } return nil }); err != nil { fmt.Println(err) @@ -389,10 +389,6 @@ func AddCandidate(c *gin.Context) { c.String(http.StatusBadRequest, util.BadParameters) return } - if body.Name == util.BlankCandidate || body.Name == util.VacantCandidate { - c.String(http.StatusBadRequest, fmt.Sprintf("'%s' is a reserved candidate name", body.Name)) - return - } db := database.GetDB() defer database.ReleaseDB() @@ -424,7 +420,7 @@ func AddCandidate(c *gin.Context) { Name: body.Name, Presentation: body.Presentation, ElectionID: electionId, - Symbolic: false, + Symbolic: body.Name == util.BlankCandidate || body.Name == util.VacantCandidate, } if err := db.Create(&candidate).Error; err != nil { fmt.Println(err) From f68b5431039e63c39d4dc389a3e2d7e591d6fb37 Mon Sep 17 00:00:00 2001 From: Douglas Fischer <34154299+DouglasFischer@users.noreply.github.com> Date: Mon, 9 Oct 2023 10:15:46 +0200 Subject: [PATCH 07/15] Smurn fix textrework douglas fischer (#10) * Update Home.tsx Updated the text in Home.tsx to better fit the description of smUrn * Update Info.tsx Updated the text in Info.tsx to better fit the description of smUrn * Update index.html Updated the text and favicon path in index.html to better fit the description of smUrn * Update Readme.md Updated the text in Readme.md to better fit the description of smUrn * Update Voting.tsx Updated the text in Voting.tsx to better fit the description of smUrn * Update CandidateList.tsx Updated the text in CandidateList.tsx to better fit the description of smUrn * exp Update ElectionInfo.tsx Remove unnecessary info from vote-card. (No idea if this works) * emp Update DisplayResult.tsx Updated the text in DisplayResults.tsx to better fit the description of smUrn May need another look * Add favicon for smUrn favviviviii * Update client/src/components/Voting.tsx Co-authored-by: Herman Karlsson --------- Co-authored-by: Mathias Magnusson Co-authored-by: Herman Karlsson --- Readme.md | 4 +- client/src/components/CandidateList.tsx | 10 ++--- client/src/components/DisplayResult.tsx | 24 +++++----- client/src/components/ElectionInfo.tsx | 28 ++++++------ client/src/components/Voting.tsx | 23 +++++----- client/src/index.html | 4 +- client/src/views/Home.tsx | 33 +++++++------- client/src/views/Info.tsx | 57 +++++++++++------------- public/favicon-sm.png | Bin 0 -> 15083 bytes 9 files changed, 88 insertions(+), 95 deletions(-) create mode 100644 public/favicon-sm.png diff --git a/Readme.md b/Readme.md index 4035bc5..7985c6d 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,6 @@ -# dUrn - the voting system +# smUrn - Digitala omröstningar -This is a implementation of a voting system for the Computer Science Chapter, with a backend written in golang and a frontend written in Typescript React. +This is a implementation of another voting system for the Computer Science Chapter, with a backend written in golang and a frontend written in Typescript React. diff --git a/client/src/components/CandidateList.tsx b/client/src/components/CandidateList.tsx index 8816477..c815732 100644 --- a/client/src/components/CandidateList.tsx +++ b/client/src/components/CandidateList.tsx @@ -64,14 +64,14 @@ export const CandidateList: React.FC = ( setName(element.target.value)} /> setPresentation(element.target.value)} /> @@ -83,7 +83,7 @@ export const CandidateList: React.FC = ( - Add new candidate + Lägg till alternativ @@ -108,12 +108,12 @@ export const CandidateList: React.FC = ( diff --git a/client/src/components/DisplayResult.tsx b/client/src/components/DisplayResult.tsx index 41a3f8d..9767d3b 100644 --- a/client/src/components/DisplayResult.tsx +++ b/client/src/components/DisplayResult.tsx @@ -148,17 +148,17 @@ export const DisplaySchultzeResult: React.FC = ( { } return <> -

Election Result

+

Röstresultat

{firstSymbolic > 0 && <> - Ordinarie + SM föredrog följande alternativ
- Name of the candidate + Alternativ - Description + Beskrivning
- + @@ -214,13 +214,13 @@ export const DisplaySchultzeResult: React.FC = ( { {(ranking.length > election.mandates + election.extraMandates || firstSymbolic < ranking.length) && <> - Non-Elected + SM föredrog INTE följade alternativ
Rank Candidate Alternativ
- + @@ -245,7 +245,7 @@ export const DisplaySchultzeResult: React.FC = ( {
Rank Candidate Alternativ

-

Voting data

+

Röstdata

= ( ; return <> - Candidate index: + +

Totala antalet röster: {votes}

+ + Alternativindex: {ranking.map((c, i) => ( {labels[i]} }> @@ -313,10 +316,9 @@ const DisplayVoteData: React.FC = ( ))} -

Total amount of votes: {votes}

- Vote matrix + Röstmatris


@@ -326,7 +328,7 @@ const DisplayVoteData: React.FC = (

- Schultze result matrix + Schulzeresultatmatris


@@ -342,4 +344,4 @@ const DisplayVoteData: React.FC = ( -} \ No newline at end of file +} diff --git a/client/src/components/ElectionInfo.tsx b/client/src/components/ElectionInfo.tsx index b0b9fcc..6da9eca 100644 --- a/client/src/components/ElectionInfo.tsx +++ b/client/src/components/ElectionInfo.tsx @@ -87,19 +87,19 @@ export const DisplayElectionInfo: React.FC = (

{election.name}

-

- {election.description} -

-

- {election.candidates.filter((c) => !c.symbolic).length} Candidates -

-

- {election.mandates} Mandates -

- {election.extraMandates > 0 && -

- {election.extraMandates} Secondary mandates -

} + //

+ // {election.description} + //

+ //

+ // {election.candidates.filter((c) => !c.symbolic).length} Candidates + //

+ //

+ // {election.mandates} Mandates + //

+ // {election.extraMandates > 0 && + //

+ // {election.extraMandates} Secondary mandates + //

} -} \ No newline at end of file +} diff --git a/client/src/components/Voting.tsx b/client/src/components/Voting.tsx index ee9d4f0..ba1d15f 100644 --- a/client/src/components/Voting.tsx +++ b/client/src/components/Voting.tsx @@ -120,9 +120,8 @@ const InfoBox: React.FC = () => { return

- Rank the the candidates in your preferred order.

- Note that the ordering of the candidates that are ranked below Vakant is taken into account.

- Once your vote is submitted, you won't be able to change it. + Sätt det alternativ du vill rösta på överst.

+ Du kan inte ändra din röst när du väl röstat.

} @@ -286,24 +285,24 @@ export const Voting: React.FC = ({
{(election.finalized || (hasClosed && hasOpened)) && -

It is no longer possible to vote in this election.

+

Du kan inte längre rösta i denna omröstning.

} {!mayVote && !hasVoted &&

- You don't have the right to vote in this election
- Contact the Election Committee if you are a member that should have this right.
- (valberedningen@datasektionen.se) + Du saknar rättigheten att rösta i denna omröstning
+ Kontakta styreslen om du tror att något är fel.
+ (drek@datasektionen.se)

} {!election.finalized && !hasOpened && mayVote && -

This election hasn't been opened yet.

+

Omröstningen är inte öppen ännu.

} {!(election.finalized || hasClosed || !hasOpened) && hasVoted && -

You've already voted in this election.

+

Du har redan röstat i denna omröstning.

}
} @@ -321,16 +320,16 @@ export const Voting: React.FC = ({ - Are you sure you want to submit your vote? It can't be changed once submitted. + Är du säker på att du vill rösta? Du kan inte ändra din röst när du väl röstat.
diff --git a/client/src/index.html b/client/src/index.html index aabb665..846f761 100644 --- a/client/src/index.html +++ b/client/src/index.html @@ -3,9 +3,9 @@ - + - dUrn - the voting system + smUrn - Digitala omröstningar diff --git a/client/src/views/Home.tsx b/client/src/views/Home.tsx index e0b2196..bff4c41 100644 --- a/client/src/views/Home.tsx +++ b/client/src/views/Home.tsx @@ -31,30 +31,27 @@ const Info: React.FC = () => { const { classes, cx } = useStyles() return
-

What is an urnval?

+

Vad är smUrn?

- An urnval is a kind of election where you can vote ahead of time, and where voting is done through a ranking of all candidates.
- In Datasektionen, we use urnval for the posts: "Ordförande", "Vice Ordförande", "Kassör" and "Kårfullmäktige".
- More information about the posts can be found at dfunkt.datasektionen.se. + smUrn är ett digitalt omröstningssystem som är baserat på sektionens system för digitala urnval, dUrn. + Systemet är primärt ämnat för användning under SM, därav namnet.

- -

How do I vote?

+ +

Hur röstar jag?

- Click on one of the open elections, rank the candidates in your preferred order, and press "Vote". + Klicka på en tillgänglig omröstning, rangordna dina val och klicka på "Rösta"

-

Questions or technical problems?

+

Frågor eller tekniska problem?

- Contact the Election Committee (valberedningen@datasektionen.se) - or the System Administrator (d-sys@datasektionen.se) + Kontakta Styrelsen (drek@datasektionen.se) + eller Systemansvarig (d-sys@datasektionen.se)

-

Source Code and other Information

+

Källkod och övrig information

- In order for the vote counting system to be as transparent as possible, the full source code - is available at github. The system and vote counting is - also described at the info page + Källkoden finns tillgänglig på en branch på github. Mer information går att finna på infosidan

} @@ -68,14 +65,14 @@ export const Home: React.FC = () => { ); return (<> -
+
{!loggedIn &&

- Log in to see all open elections. + Logga in för att se alla tillgängliga omröstningar.

} @@ -88,7 +85,7 @@ export const Home: React.FC = () => { {elections.filter((e) => !e.finalized).length == 0 &&

- There are currently no open urnval. + Det finns inga tillgängliga omröstningar.

} @@ -105,4 +102,4 @@ export const Home: React.FC = () => {
) -} \ No newline at end of file +} diff --git a/client/src/views/Info.tsx b/client/src/views/Info.tsx index 01da672..c3979b1 100644 --- a/client/src/views/Info.tsx +++ b/client/src/views/Info.tsx @@ -18,48 +18,43 @@ const Info: React.FC = () => {
-

What is this system?

+

Vad är smUrn?

- Durn is a system for allowing voting in an election over a extended period of time using ranked ballots. - We use this system for the posts "Ordförande", "Vice Ordförande", "Kassör" and "Kårfullmäktige". - This is the third iteration of this system, the first having been hacked together in a short time - and then stopped working after running for a few years, and the second never being finished. + smUrn är ett digitalt omröstningssystem som är baserat på sektionens system för digitala urnval, dUrn. + Systemet är primärt ämnat för användning under SM, därav namnet. +

+ Detta är tredje (egentligen andra) iterationen av Konglig Datasektionens digitala omrösningssystem. + Det första systemet kallades för Mentometer och hackades ihop som hastigast på en pub. + Mentometer fungerade bra i flera år tills en omröstning i ett val där systemet gick sönder och alla röster hamnade på vakans. + "Osäkert", tänkte alla inblandade och dödade Mentometer. +

+ Efter Mentometer gick sektionen över till VoteIT, ett system för stämmor som THS erbjöd alla sektioner. + VoteIT fördröjde varje SM med ~1 timme på grund av hur dåligt det fungerade och att de ändrade sin UI mellan varje SM. + Det enda bra med VoteIT var att man kunde vinka genom att trycka på en knapp. +

+ Hösten 2023 byggdes smUrn till Budget-SM för att slippa VoteIT. + smUrn var kraftigt baserat på sektionens digitala urnnvalssystem dUrn som byggdes våren 2023.

-

How are the votes counted?

+

Hur räknas rösterna?

- Exactly how our urnval is supposed to work is described in - our reglemente in §3.12.7 Urnval. -

- The system uses a vote counting algorithm called the "Shultze Method", which operates on ranked ballots. It has quite - a few desirable properties, one of the major ones for our interests being that it produces a ranking of the candidates. - Exactly how it works and all of the properties it has is described in - its Wikipedia article, - which is very throughout and descriptive and describes it much better than what could fit here. + Det alternativ som får flest röster är det som vinner. (duh) + För att gå in lite på djupet så används något som kallas för "Shulze-Metoden". + Exakt hur det fungerar kan man läsa om på Wikipediasidan. + "Shulze-Metoden" är egentligen ganska overkill då metoden inte spelar någon roll när det bara finns 2 val men används för att dUrn gör det.

-

Why can't my vote be changed or viewed after voting?

+

Varför kan jag inte ändra min röst efter att jag har röstat?

- This is due to a balancing of the interests of security and having a good user experience. The simple solution - for allowing this would be to store which user made each vote in the database. This would however allow anyone with - database access (through a leak or admin access) to see what any and all persons voted for. A possible idea for - a solution for this would be to introduce some kind of hash of the users information, and instead store that. - However, we don't have access to any kind of information to create this hash that isn't either public or not permanent, or easily bruteforced. - This could be solved by having the user provide some kind of information on their own that could be used. This would - effectively be the same as the user creating a password used for voting. However, this is somewhat overkill, and - having such a password was one of the bigger usability complaints for the previous system. It also brings with it all - the security problems with people choosing weak passwords, or possibly even reusing them from other places. - The last point in particular brings a lot of responsibility in regards to the security of the system. -

- This is not a perfect solution, and is a balancing of interests, how this is implemented could be changed in the future. + För att det är så dUrn fungerar. + Vill man läsa mer om varför dUrn fungerar på det sättet så kan man gå in på dUrns infosida.

-

Source Code

- In order to be as transparent as possible, the source code for the system is available - at github. +

Källkod

+ Källkoden finns tillgänglig på en branch på github.
} -export default Info; \ No newline at end of file +export default Info; diff --git a/public/favicon-sm.png b/public/favicon-sm.png new file mode 100644 index 0000000000000000000000000000000000000000..95a00acd37ade2c179882f345d8622740fc65b30 GIT binary patch literal 15083 zcmVPyA07*naRCr$PeFvPKRhjqyEw@hZliqtsGU*+Nf}pNzTXomtX;^HLYBkPo!_oMkVYZV|vwNd1nlAnSVWqccgTbg`33TJ`ol>DuYn%XFfxAu*UR4bjQxLkWO%qlex--B8Ka=*i|< z?D{DNS+-N1`8Ym_T(7eUhKWF{231zzl@-YJ$Cdyl5{8bXu0b<&L<>~NvINs0Fm%7X zKLUVj4l-HfeG33?xjP?Py8J6y28tsFg+xG>HSx)=WJj4B|TN&7%0faJJCTx>Ie4%0ND zC<+E7{Fp9R;S-4gxXe?DB7YE4&u11hdCZQ?{B$vxZS4eftF8lk|3{mC})k@;nX7-mv{u5GYkpBgK}-x(uug?|VN3JbXJ)vgYM1Ga{!R;S>$@E`sR8UtryVs>sq?l zfa+0kd88UQ;4+N#mqYS%lLlvev5x@IH<--ks(ke;s*GzAgKDP&yQc07k${$i5JE~Hf)Ng$kzp~;A% z!HA&ENFWIVd6EaEasZV|9tve2JU%ZZpNIK@Jbc8^#K1(?iHFk zJoxgF1juIQa&(Nrto%=DLvWip1r?>`+;KNdW(Z2E!dA1H(9jSKV|TJ1tNiD%S8hgY zJc_uUgk~BrOoO?GK3PS9H-HgF5oSe8G0Qgq{e4A1z{8D0x;DcAmj*_A94|<_@E_^{ z#9~nn0Om5L*#OKb>6kM}AB0TN!F07r=VZ!1WE#*8k zD#`%O-~bTn+thS~+ghq>5brYpA4=VkI zn6D1R-^K=EaIh5cC_VxKo*q3H^rh!>q~I44Ar(gQ0Wy56^qpkT``i-U!R1S!#hV zW|^&O6S}P_$bB@JJST7f9*+mp+l%onjKg&Q0Lb}%XMvMDDNATN*MP^QEqGHpD(r>S ztllLHfUeSq*2HA8?lZk`HrHY{T3X6AqiB;Pl*c{zo;CqrteOIN6(?>>bJrfn-nO%N z%Gip{{zk42ga~G4fn^(r2%iOjlzXw?2rx1X)5Pd@KfW%H!>98{LoN&mH_`GFO}y85 z3b#qCaa?J)gGM&kvP>IabP^z&N&7$mAOqLxLvSyqqp-BtnNXTR&A`saV|d=!iS_>a zl*#AzV8@(0)9o3#1RMmU5-6lRCK|*)NTc!jl8M~Qumm5e5Z%CYu`T#H_8^%^vY%ve zua6`^Co%qP5pPQy=F*v{h^ts`&c)2qVO*q>KIXazCmYV;31bUZ`A&1AO{`>VwlV>r zGmDf1gWC!W3aX3|VJ{YYreRjeFi2jNO9DRIIMH|(i@ocyM{i(iL?*jJ5HqqNj$}I? zeUXpJXaii75L~!pae0 zX8PCMWI2{9b5LDf1;lk`R5C9ip2UyL9r%T`4~c~4N;7509J=TL!0alQ`;wV*m7Nio zeyvQ#WA`?IgwnWzJB%89s$?8D##EUDK$5Vh{y1*OYMds6kBm5`qTOy8|9cvXt5cVh z8;UNkfBAqoG6G4g zj+Bb|IAeaH#tU2MR-P= zkAW4{-2D_@2nC3iV>4ctk8l+v7SScC0{U>C?MtTzIy#dw`~(@Z0|W6dv3Zd4d`_s> zG|>=h#`nEzu_JlT3IEy-YD%^z>({bnhbw@>+(y$tSwh9F<`jIk{}d;A#3tLs@9TEs zX?Z&u{iGfQ4NctD0zx*Fw$7wvZ{6&!XlNfuASfG`&zUU=isHdH<*~TQQ^RyV6a7q1 zOU&|LEUm>UH75M@j@EwGGw(W3kc3iBHCXTn_#0ywmK9zBi6T-~{y>qiqwXjcV=c~l zc%H!ebe2r(&2#1YoQoa+G|v?YFYd>5EUFyqWb0@oyb4yG-iQ0mS~Lf^<({igCIFZ$ z`){yR^Dp$PILAN$k)nfj_4W8?X(f(I;Y%9;*`$NBC4^*(7xZM3$Q2u)1@RbWVM_T> zmXINfp8a2u@#eYRSZ;1cOVF0JEN0#Xjjzkw+XPUHQs(9aGJF{rD;48q;~K~XekZ;l z0kM`={FC|)cI)+*HURoE4xLJ~lU$6hw6LJc6LdUI9+!(CIn&@$s zX{6(cJVO6xzl&T08MXYJbWY=W%w#W5y3i9k> zXOrC;#r1i$Sjzw)TtU-BUNC@ykb$#moU05|WEa`ZdaJB@Ipb-k^_mivtqnlxguSN4 zyrPu4B@%>hO{-*Rs~6u;CLkzz@F=#RImquoA5~TH&lryz^Jj41+a`Pf$D7XKdU+*| zD(&`7GS$Z+2LRx~BohRHf5#_Kke_d>0P;+XwivDtuEW~cakknC1oWsM&mV!mYv_*` zjh)yT48ydah5w#QV0veN zb4Wyy>wRmnHhPT30a#u@mT+y+Xgm>}g*|O&@Gsbnm7cRgx(V7DlL~^uc7Y|r1q6UQ zG;6)Mfw67*_?|KaSL6+Y9PpyH@euB00C0;(1D7ckcqw@Wf~AG20hpwYSDSX?Ie7<@ z8axu%u|n370Ot>Yf{2M*Fa=*Mn`G0+XepBfxY4&3s|5g9I$Dx(ZP6$^q0MDDITdQa za$_S_`_2iKMN&oK(u+xh3#$@x3V;sImx!tJLP`z*79S%6Nj5O9tq^yqGf-1Bh-dba zI@W}b;68JG3IOQLs)QGhn{zO=d>H5Jk#QxT!eg4vwi2!3{|(1hvPM8=Rem z3(}2pD<6LOQgbbCJT^~^#|$zUfb6-nSG?;5--Yyb`b3D*>k!ixB8#w4QKhr(gp zt*^lv&lxDP%3?`&@~W#s#GcCcERN4P05Wmeti%kDh!Kxp$EfxomU$OoY@m{}ZHU9C z=~&xxm;sX1ye_m0I! z7}PN#QiT5_jl(?uAQbxZkc=mAbD$Qr@so^X%(8`&jB5%L!}o!dR+r zz)J5K;T_0M2Kzjpy(gz%KmhP~xha_#)fT`_N)4{87{je%L98I^^}Edn@qn>`8-cD^ zJPDAAN*P{EE<{0IzFp13Lc;+s_MJG4yS*E6NFpPvCqt0=*zN&fFc+^Z?#00TQuard zRjkoZAsh~~C;&+SvH`9t9D%2_d0b-Aa;HipML|PTBkoPEMXk4ic|=SxGLs?)FOcOB z!*|}bsLcuBTM+Y!hl^cNs2CJro6GHg=66mM- z`s3&3#~~F5n6%&kP&H|u;qzQ;5ur722OO#DGTx7Mp@CIDY)3O-!}8L8J;i z9vNRYN8v7MI$+f>l4nHu4HG!k-h{>Rcd@6yqL_D<-tJ=nbT>GBnmShfW;&0g+~>`a zxYMYCpY*mpaNH$2T6Y@v#MfYVfa3pRlI-{96E#vQ#ERr>RE5TQS{gH@S?N` zBPs@@)CSP;R1&tT`5>P3Zet;0nr|)%Tq(wGUNUO{Fi{SC!5oF}>f^DrU^j~dawFEd zCr@_KELS8vxWk-^YfHEh%sd0iVu|9G-!$yRk3D-(@9Wx-?NcMLv$4sh=FB?R3W5wb z3-Gi)AHz!faj|cGWC@9eFz(dXa9^0Y_M&l%eHdRqqorazugyoLuK|iyUdXF^tqLKc>xUwE-m@D&llDMkUV*R&QY1{5QnpKaiB&IX7#bF6{c*1{ z0|UK9xXE9OHId`O7I3Agnbe`+m5Cv(emv>F5@SmGa|2iafWQz5x8Xsf7Ju@bb|uT4 z=VT)y3RkmPsNEftbsV54G%gkgH%<8cUVKxUh#TZdb`0Mde-e97MmOMPc|SCAjnKKy zBLKXh&qq~N6&lagV}-O0f0R$4upPJslktU$NnD@0ri_fN=1>UB%uV>K=L7?oBvWe` zw~Fa}c)H>i5zY*Q+bSd5eR$A27gGucG7JBD?;5O&o#Z`+I?#|g2x!t5Ci>wibs>CZ zh1@6CAoV%93E78kVEV9REAHLr5dh{UD)Epy3j>NP_)jz- zBIvWX=_GE!yEyBO@cmsh3tt7tOCvt06b{(ZO;_kU`&LRPcP<_ zaJRIy;2wPqYW;OE4Jy;_&P)Mp08~~o3!g>Wd@9=Nn(zZ`#_yG57}J`E>($A)yl4b3 z;I!OA!P$?A!i_(Ak3+W;M%{xiec~y?H?}Pg4|!%|T-hKy!^aG>NCMphfM1ar$ueg< zJuh93ais&DjwC_~Y-%`+hm=h?sgjy>3S5bAn8mPlKd`j)GBu0t6#$q&%tc(kWDw6u zmt$<{K*#SF*6SN*cj9SUiPW&c$Zi6d4Iqp_7E2e_J{e9&n$O`eV-wc<>M%Q2iQAQF zwsmb&w#B_qwAA5IV>8zJ>Np{zLpZ?gDrOo#gQcafOOC_mDkt;uFfmy@1%QA7a}U2P zk71NIiL#2jxMJZ}O4~~Kcw!)y$#dW@ zE^q)KNr*LuaARU6Hu}$TvCoXZ?gpJp00KaDRW&OFwTm%m)#(OygiheW*cu!uh~UcP zAl$FaL}gKFYWcXZ2@W)$#xiXKb_QF7o0YD9(z%1qP%A3%6=O7R*C#_Mq5@T(h_WPH zPb7dfjMSrGpdP^E#ynhBIDjX}MByvF>+c$O5VH2HXH*-|1|a$LKW0$IC(44GX8 ze<;r{NvMc>@qKMFK3y`Nmr+~3zhPoi{bAf?u0eyJDrhZZOz1?)2xc@WfN@d@p4aC& z0I>Xhv9gV50{v&C{5jrhI)!^Y z8}Pmv5&)pih<&UAx`IuoAdskWlZ@d;5gtv>#+cFp{4xRndUHD-mA2p&^L@L40;hxX zY0uO<(0Y!sD8`TV1*oj5|XIQn5)1o=Yx7 zKk9Zyi;a>V03yv{EX78=DId3EC4KG%^bP>Lgyxg_P%OnPc#A0G=UBCe8ct!crxu40 zc9otx6}UYH0PE-};tC=W-`LWG$FUV}sfRJTtq`{=(=e}mBu^L#U)xIBCqwPHM_-4( zcqvF^1#Qyf5fpCG&@bV~ZIsKXoFIAz^4L9dBMAT&1-vbe8@+2f^#YiM)WP8)(g;u< zQE;m<1%Fd{86SS(4G>uW(7X@JjSXneQ+cgH&cZ;Q7=hg?j*JHwD2d9fXW!>VWRA&n zdSS!FpBndLg}e>*-b9zHJ=3K-NB}b6blvIUXGsFn#J=WJSemTG{(@G_j#l7)Wi|$u zRyqJ+aTwm(TJL&?drPn8(R0WIb8{Af0U) zrI9#pAXcz&12a!}$2TO;f+qIU+F&gA&4=XYYQXxC8o1tEiF0a#-@mVoz}^C&f5ML+ zn6ojxY#7I==*&qCn13K|!K>0C7&>j|8eBpALpRgM1yp7P_5uK@-i{$ErA!n?B;18*_)PJ5CwV3$)hp+=;(2Ko+VVZn z4DI3q01v7d_=YkLw<}Y5-Mjz*4bmU$_uy$|JI;AE-owXg;GE(G^bP=aeLzCv1XHr8 z$S~6k9Bn#_`;%+1E3XL?+e@+3GZ({4s#8k3d5cg3Eujz|)YoB^?%D5Mq!9V6N>@pi)jEHyWxIbgYg zxd{bjO$v33{|>wWNkT!p!TR@os$v2sIaujU)5H&>oA6)KdxEV?@gK_u=)DABd4;%! zUF~NuTrELH-uM6+38SApp+GIoNU* zOOop_G*F5=jHxJMZRdH0(P|t`!I>ScCo#cO0Z(CGDz&Xw0C39B=>?{HtMP1X4tqum z-1IJc>yP7BtifqD!DDDSDlN~dRjhk$W^8OMNo;$T$E3O3?XqLtJn(XZ@-C~GwL-7C z1i&-qHa0=u_fF%1Cq}InPb?~SUuoTqe^qv)B`<+5o8xh(J{1b7Da#-f6+7f|?FpSi ziR`zVQL$da7GPxwyXueOWoOOLY ztGDx=c}qy<0ss`q7_8;t7x*~JOUroig9y0Pg_`hfs^6E|oV>rC)NrUxnO?|}a$_hI ztj$MNMJ2m7Z?eD>L3jIV@m6>*ODtWL9D;kKX(%fyvG)aJ<0rh79nHt_cybf==7%t` zy%>*r7hq6P1vln68DEGf(Iiqx0#ajdH@=5Qq>X3_Br#Mk#LKt_c}0a?ssIKkF@e_m zb@iS!tral^_hTj&Rg87WqO!!=&wM{`16H>kV_Ty2sTPp?$2cFSUICia7?0 z^(j2HYUv!&T8^Z`8x4E#tg;jJz8J372jfwB9y~>PPQJi)Ed^dR13Q|I<0)+mwg+2S zCzGLOAzq;lAy&4tlQ@9;QrQG<2ybOE8TXpgaaF}wembdASw^xof?M-8Vif}bH%fEy z$%zd>I41!7%v^-BvNC4)SrWhkz;U_VcE6mo=6L`>L>Vmr5UG@Gk@A>_8F^O~j0F5D ze-5(b$um%8m@N@JWp2YS^j$1<{WWOVAg_(a7x(SXvSU6bCW5ccoXW6(9rWhDtf0JsKzE44MkJ26+7IF>N?`ejzhGUT0Fd8j z0H}z`43JlqkF`~uBLUW9Ron5tR{>p0fD8a2H+f((fd6ZLjP2fK6;03zU`C+y4vtEs z0#cn<(qp~00Wt!>3ep=;RRy2ZM&KSRswRG7R=!{-*ESr&6UmJ@m`_Zi0k~J5VL$(_ z<`Y<++<^W0ZKC%OCynqXQ5*oQ&{OE?Om1LLB>>qhRS6FsGG|~x`6%bwk_4?SiW{l< zMD)aYHNs4{8+yzM%+&Xm06=cwPozZ{R@Tq%=1DKq8V=)I@=EMyjF!Fx0O$Pk2qbSt zQ4#at2IDe(HgBx6=MS-kNeYN?^l#7Yz_aoWgz`*$))?XIYNCsPU0)^>c)w;Ed$=Qy9DF9SiHaRNJUdQ6!t3D3z_U`p9g zo~swpGskAzflh(Ns&0JZf2|@^1e7I2HJUqmEa%}4kP7%UuI)Aw z*i#iiPd!N~!!wDwY%fFx0IG2?fn)6rxJg>gY$;9%xrp3A0stk0?v$o+y0o=%BO7ht zNAa!rnYo{lLEN8Q0pO1<`)wo5iRW;oPs5}tGOkSY!^83{6ciS8+(1f}u(|FK9@94B zKwcXplROIEsnkut?ppxx#+dU1gR!E0CQELyWRM7FZ*M$`yHShN-X!Zq(5EV(rxL~L zhx1i=49jP;UAwJB3xId(4={J?oR+y6o#FR~ZU73hm z%qhH5h(3aE@aEZFjPKawr@~vuhUw{CWn~2R^r99cXqvYQ&&KAWsI1iKgCk3Lzu^?_ zRM+7Uc>?DP04owGynt*0K(1j~T*ZCnOnkg#G!OiUl5*C$i`IH9iD#wl_;+JJGyKw* zS)T$xGEp2=uvD6Z%gaVN8Axh?rRi7^*?|Al_i;iECnWZCnlc`rvve@6qG3iJo+AUM zdZ0t!_Q;4ggmIU#7Aw^=DJ!*C5};E6bXH#OR4fq)#zl&7pJx^(mkxHeowMo;Oq0dj zA2iqFP0uOTSAYc)?d@f`^*!9kW0AmYjlirVfMj9-xzf^A=ts;awjC}%QwvA%9p5{w z4ZS3(qTOB}0-&dqFkuQaSMVXs#O39qojNyBjPY{AHvF5sH)Us!TmztEwXZ`s;no0m z5og3Iu)=dWN=vDFNVJzBi6Zs|K6>^P9@RHvt;#oku&&G7zV|#1&@})!6NqfmX`cRg z$yfxbz;8=XvI82z&G@#o5{KDNB)otoo7_S+A|fY@Abum>`TA?pINTxCKni&5`;*wG zpvyz@Mx66m8!dByXB!BzZGbK$K&EnUXLnHRPJF;WXO70h%IuW*fX&6%b*yhZj3*MC za4;|KR1?e8(#6CuJ=mi_P&NQi6bb)`ak$MpT~N)$mi4-UElo#phq=bCroKo4z`JHl z_4dP$6Z245T;%MtD*<)Qjd)nE#agNg7VThoT%mh+hO&XZ1%T~}F*=gCmqY|i88?`d z@a5om$Yc~+k3sAp%C=K2b7; z2Z`t-iZkhOt*H%9D%7TXfkVxCtn^|=Jd?!h}AMH4`-)7NQtO^J5*=7Y*u!@7a@!)I|5 zR^p6@#A+`9P{s)%LgTmqa3FPycy?2_2k$&xd;&-Ul*EANk&8^^Gh>9jPo9!!S)|yi$;eQN3NOLi=q-1V=6vVG>&aP!RY6r{_s>w18(!y z;sC|}UA>KX*Y)nNpUn|D$pv)0k%2E^BumA5SYKd~fzwSS+oM>DP57O3geSVhtY>bi z#_>Lq`U4OEDujPb-|9LVTSK_f^DcH9yaQOa@$qz1ftWs(aSv`YCgW3O<9X)~CTFeg z-2iO|LlpG?lHW%x5kXbbho^7_YO03WpyU9cCQ;NSth?do)b0uG1$fc_aTFC5rUuCj z1HY}`iD%_qX!2R@a~&#OHpX*zPr%kmE=ZvrZe$u5tOxP1J_9q$M>vL(s2AAZd#Mp>G%STv)r%!7m@Bn7OXGskz4-b+A=pFzhb&6xW6wEHB z5iDR@453g6w)P6wy;y^ieImB?vTLt123v zGckymL{=_9tY%UL+d?Ps18oBi1Y6NBnTNad8n%A{lU1=~)+&Giyg;kMbDk@mtRia| z$-Na~AupGfVN@fFl{vGGPWE4;g7azY5{coUiu6GQZT+^bE+?6TpuEnxNW3x`6u zIrcWT2AW0Y&6*k>sBu!qTm?KwTeg>1F@FB`=o$PNJFtO>n$-MJBJ!ux7QUV1?AQO-1HO|WA9g;^x{!-E~ZtFaNJX769Em4jaa5{#7gg3q5C@$X+Ag!kdYH; zji|L$tj$slEYb&Jg|dKCuZ8^O-LXwk=QiHBuH5sLMelrLDU zufbpBXX&LEPX?Zxm@wzLi?$En&XnnS>j>H8xw>ZZeV3@au*h_>r{J zmW~d1E_w+dR^fzYwT{})^+)_)5aA9u*;KGc+Ptzl;^(!Q-RJP==tHs@nVgwDV83fI#W}rFLf(P_ktoG5?Z$Q6f0O4W}q7hP09hK0?c0ca&%)rdDVa{%8 zqKuvFjFnCAV~NRkOiM8#?3Lh@)w{M`@BMV;-VN*s0FtZ}9xjimxIw!NpDmu?bk<@i z0ZzR-*V2eP67OP1fPx+!bNd$(0IZEGn?IA~Hg4A@;bUcEoSn+VkW&TJhUO!<54EEA zp$tvqK6@%q`9={Bn=>)LNRU%4ttBdvjy9ZObqc%l+xWi2nWojX=kP@e06u7z=41b2 zyJvD>jBC%wquzxWUL^M1CG^vPiJo}x)*rx9c@ye9)I3pax8A{=>$tMmH)(pmD86qE3-UDpX;8jKwZ# zObvPzM4G}_f{l1f^`mhaK_tRRa={CyL-7DI^_e~%u<@Qb*mPG%;WIrrZ=AyK) zSZt)o0YE-BB~$J%WWQCrr)T@Iiwbn+*Sg6x`DC_s{^gyP$*}V-Yii)pb4DJlh zzj+<3~S|Pu$=m5^kKLU(^+}7#nETiNa%Q@Wgk{xJHkPV(er(F zXhl(zW>uCD(Gr3f;r6XLc_JHfNwx!J?-|5;_{r#>@Zm{w9>$jru#+{G>lP2S;Q@0! z-cpV^4XEv{>P*ZE48gx8E{9y2XH(^vE1b~r>k#dhw$t`*Ea}lzr?woW0l1{j6bkMg zC9IpFwXzqI$^7guXv45ruYso^;51)M8dwuLjQfoB2Bo)QM2t|2aYAp! z+rBf{s-43bH7-)Ld`R>x_5qjd+n!sP$4q-)NBhD0)R7-80-<34u(ka_F>H4?}1OO}C z$_JCCO7%$i2FA1geJt2QUuy>2`0*a27U4Wn0XhS~5F>~c#$1f;H-v>-DG>8+>^PoH zZo{!chicw;0N~HvU-#i*eI{m?k4$NTMTRVOB)ikQ9{aRL(Ta|woro!pI5~fv{ATKpNv3R5!RhKfW`V+wC3@YEJ=aE1_!{H z>cKo2B#f|@a0oZ~)?$~|z**i{9iu_88$f%Y$v!}u3{2M!j{K8Z9O3QV?3Xb%98yCz|oZGfJ(itVX3vYEQH zZ;sd;;Zyn$+-gq7zGMUL)oan3M>>)37wiTAMd0jQeO$vm1zYfD%X=3L02K)Z_Zl;J z44gfgYP><{6cW^{*<6V^sAZ7K3-khL#1k<2M%! z0K6!3esC~ekQQ;BF3QTt*lP>7;ya#I*rOAP!}?Is2ajx0ikPgG^r)sy^bk#>vz)t} z9QJ_%z?PG)Y`J^Obzx;;s2;?#=6sB(8t62j5+=oR|8~480FcdyWZP>$^WMQcGC0*+ zjh`7;v8bNaY=S0{nqq#(znYbB*+R;iaH};Qi;U*bSjpz4yEx_|06;fyU{X9jfXB?) zPS*@(@>5n$!V8J*_-T@>fNVZId)WPvodJNt+LJw1_&EW9s-NgNL>oG4hCy4$*2ZW< zOkt`>6x!v%q>J&B5ddBFq3q5;?2DJIPIz&jF%?%rIpY} zl(5{wfcg-B&zOL(R8ModXVMTW3Rc&@hlj8c4Qv zqf~KSV*{>J-oYV+TopV70HLph^huvUYhMKS(`Wk5`aRos=zcVMv2kSQeS)&NUGI*e zwe%Td2p;jwV*wb;_a-jEzNVA7L$1XMC2B{}y1GlYgO^bS&^)U$zCk;X_@@go@9_Ay zHyy`aSjYA=c1pND0zmI(-F=p!W4uy^pJ-RG>K%7Cag*#xg&ew$*BW-QE~FtwQ|EP$PI6rE`7+SeE-4An(+!`_fA-1XGf=I0@eoxT zmGS)u>@DdGllA|7+}wiJVE5MD`2c_<{k%j4R;UY*SIBz=vOYeF0?G(dPX7<`G26{^ z?&V}IGkGBw9RRq-%4}+U5Jn)i?4mUC-{?cJ%sZd$r^Advv29CRQwTRF-o_SxNbK_2 zIS{hV8FW+u5-JiZ?lx+0Rlf;*H*^YVvwaa1Y-u>cHhMm;T5WJFzM>dSF`SnS0CKW& zb950_%tI3l*X24%w1lq)!0f2xIOX4wUGk#?rWKlk=+|4a*?9DPR1r?B^dCS>lxHRHql5a_u z;U-MxyJ}g+HErn78pF%sUHF;jJ=D|YRKi3{e+V`yomLIFjuw4`NS6~kX>z0h4=J-S zw4};z-Dd6SbFk?&?oig@AVOkWT#GE=$WrT`ndIt4=LY6%=5lr+K5ewLM;b+VM4OJw z`VC`wb$k0*SwgZQjDPhUz|Z9aqQR5cYQ7iGK`WWKc^;1kv!kWBQJRE_rGxnWT$@W6 z?J+#8t;ZYcQ6w25RCX14ay~$|Ql}S>dETd7{E9KP-`E@zQMinxz!^ z#ORG}c+s~Xe~ul-ag`c9^Yc1L0vFfNpDEN51KfY~Fz$n@@n(iZ-K`>%x zLc{9NA^bqziZkArix6tv?-B(7?^&7Ikjk`iP~9KZ1i0GC z5daP}oWxW5R%{G5GN8EwK9+vEBqczfZ~}Wc$z30AqC6(!PIDTrEf@`{(9aLIDBDzJ zs~c!-Z^N3{ar{9&f%nmjh!#gUDE29`qH~ONLJ>Zhm9drj^D$fQhfl`_Vyw5E0gTbs znJ4Fn|FiXVSY~X*8ebhdzvJX{{r?dFays$ui|H8MmWOXE6LC%1IBpbLy@-X2#?r}2 z9pPvj4keqhUu(p{yaCoU$V7!DsSE=CWqtd`HcF{w3lCboI&{1Rm9ch3)0|k}(2v z@`rHa(2@dVpjlCQ*MxVts?{`F0EU4lq2YL>3BU3l#$V${an>6b;+{{eHThi=P23ef zxtzZ9zt)EU0K(oT{vBjg#8fQw4Z;@_LowP@2BpAnZv@4~Ip2%7!^irHj6w=C>n7UT z+psEj9RDL9!q#9D?`>!oXr^A_BLJK~7;|$45x}WV`Y>KD#kJZX)Of4WFHpn{MH2r> zop<)4P%>HgKEc_GCJ||E!`@^A{u(%ojrv*CMOwJ$z#4{e@+R{pGhnD|qDC%h9Jv`P zwjTPVOIYrn?>w($BG_giU-hBTpNIaz5{yq2U})G2zobBsWay@Wn5koLemnN6A)IM% zLO2mcTTod3wiW0+O12rr^L+RVa{7lF09J&LLb`4yZehgo+bO<78!!Ojphe3juD{G` z?}*Z!>GPonKu`A#ydY4`;Z0b;&*i>R3wZ0pE0W2!rtip(nD zLl1zoo&$WL3*9b$bEkCD2mB)dTtvL~T&@Q9wKCJf`4Jxh;3EKZ@B%(mDxkBSLkayK z2q& Date: Mon, 9 Oct 2023 10:17:31 +0200 Subject: [PATCH 08/15] make comments work --- client/src/components/ElectionInfo.tsx | 28 ++++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/client/src/components/ElectionInfo.tsx b/client/src/components/ElectionInfo.tsx index 6da9eca..757c920 100644 --- a/client/src/components/ElectionInfo.tsx +++ b/client/src/components/ElectionInfo.tsx @@ -87,19 +87,21 @@ export const DisplayElectionInfo: React.FC = (

{election.name}

- //

- // {election.description} - //

- //

- // {election.candidates.filter((c) => !c.symbolic).length} Candidates - //

- //

- // {election.mandates} Mandates - //

- // {election.extraMandates > 0 && - //

- // {election.extraMandates} Secondary mandates - //

} + {/* +

+ {election.description} +

+

+ {election.candidates.filter((c) => !c.symbolic).length} Candidates +

+

+ {election.mandates} Mandates +

+ {election.extraMandates > 0 && +

+ {election.extraMandates} Secondary mandates +

} + */} } From 8ab95180105abc2bfa8d53775d773f05f0dc9a54 Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Sat, 14 Oct 2023 13:13:40 +0200 Subject: [PATCH 09/15] Use transaction in transaction --- server/actions/elections.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/actions/elections.go b/server/actions/elections.go index e6dcc06..a384e19 100644 --- a/server/actions/elections.go +++ b/server/actions/elections.go @@ -91,10 +91,10 @@ func CreateElection(c *gin.Context) { // } if err := db.Transaction(func(tx *gorm.DB) error { - if err := db.Create(&election).Error; err != nil { + if err := tx.Create(&election).Error; err != nil { return err } - // if err := db.Create(&vacant).Error; err != nil { + // if err := tx.Create(&vacant).Error; err != nil { // return err // } return nil From d39e565fe91f0592be952c5758dc1004add9f88d Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Sat, 14 Oct 2023 13:15:30 +0200 Subject: [PATCH 10/15] allow concurrent access to database --- server/actions/elections.go | 12 ------------ server/actions/voters.go | 3 --- server/actions/votes.go | 6 ------ server/db/db.go | 7 ------- server/middleware/voting.go | 2 -- 5 files changed, 30 deletions(-) diff --git a/server/actions/elections.go b/server/actions/elections.go index a384e19..b7dfef5 100644 --- a/server/actions/elections.go +++ b/server/actions/elections.go @@ -70,7 +70,6 @@ func CreateElection(c *gin.Context) { } db := database.GetDB() - defer database.ReleaseDB() election := database.Election{ ID: uuid.NewV4(), Name: body.Name, @@ -135,7 +134,6 @@ func EditElection(c *gin.Context) { election := database.Election{ID: electionId} db := database.GetDB() - defer database.ReleaseDB() if err := db.Preload("Candidates").First(&election).Error; err != nil { fmt.Println(err) c.String(http.StatusBadRequest, util.InvalidElection) @@ -179,7 +177,6 @@ func setElectionPublishedStatus(c *gin.Context, publishedStatus bool) { election := database.Election{ID: electionId} db := database.GetDB() - defer database.ReleaseDB() if err := db.Preload("Candidates").First(&election).Error; err != nil { fmt.Println(err) c.String(http.StatusBadRequest, util.InvalidElection) @@ -219,7 +216,6 @@ func FinalizeElection(c *gin.Context) { election := database.Election{ID: electionId} db := database.GetDB() - defer database.ReleaseDB() if err := db.Preload("Candidates").First(&election).Error; err != nil { fmt.Println(err) c.String(http.StatusBadRequest, util.InvalidElection) @@ -248,7 +244,6 @@ func DeleteElection(c *gin.Context) { election := database.Election{ID: electionId} db := database.GetDB() - defer database.ReleaseDB() if err := db.Preload("Votes").First(&election).Error; err != nil { fmt.Println(err) c.String(http.StatusBadRequest, util.InvalidElection) @@ -286,7 +281,6 @@ func GetElection(c *gin.Context) { } db := database.GetDB() - defer database.ReleaseDB() election := database.Election{ID: electionId} if err := db.Preload("Candidates").First(&election).Error; err != nil { @@ -302,7 +296,6 @@ func GetElection(c *gin.Context) { // candidates in the elections. func GetElections(c *gin.Context) { db := database.GetDB() - defer database.ReleaseDB() var elections []database.Election if err := db.Preload("Candidates").Find(&elections).Error; err != nil { @@ -322,7 +315,6 @@ func GetElections(c *gin.Context) { // published flag set to true. func GetPublicElections(c *gin.Context) { db := database.GetDB() - defer database.ReleaseDB() var elections []database.Election if err := db.Preload("Candidates").Find(&elections).Error; err != nil { @@ -354,7 +346,6 @@ func GetPublicElection(c *gin.Context) { } db := database.GetDB() - defer database.ReleaseDB() // election := database.FetchElectionIfPublic(db, electionId) election := database.Election{ID: electionId} @@ -391,7 +382,6 @@ func AddCandidate(c *gin.Context) { } db := database.GetDB() - defer database.ReleaseDB() election := database.Election{ID: electionId} if err := db.Preload("Votes").First(&election).Error; err != nil { @@ -452,7 +442,6 @@ func EditCandidate(c *gin.Context) { candidate := database.Candidate{ID: candidateId} db := database.GetDB() - defer database.ReleaseDB() if err := db.First(&candidate).Error; err != nil { fmt.Println(err) c.String(http.StatusBadRequest, "Invalid candidate specified") @@ -486,7 +475,6 @@ func RemoveCandidate(c *gin.Context) { candidate := database.Candidate{ID: candidateId} db := database.GetDB() - defer database.ReleaseDB() if err := db.Preload("Election.Votes").First(&candidate).Error; err != nil { c.String(http.StatusBadRequest, "Invalid candidate specified") return diff --git a/server/actions/voters.go b/server/actions/voters.go index 50c6ad2..22040be 100644 --- a/server/actions/voters.go +++ b/server/actions/voters.go @@ -34,7 +34,6 @@ func AddVoters(c *gin.Context) { } db := database.GetDB() - defer database.ReleaseDB() if err := db.Clauses(clause.OnConflict{DoNothing: true}).Create(&voters).Error; err != nil { fmt.Println(err) @@ -70,7 +69,6 @@ func RemoveVoters(c *gin.Context) { } db := database.GetDB() - defer database.ReleaseDB() if err := db.Delete(&voters).Error; err != nil { fmt.Println(err) @@ -90,7 +88,6 @@ func RemoveVoters(c *gin.Context) { // GetVoters fetches all current allowed voters from the database func GetVoters(c *gin.Context) { db := database.GetDB() - defer database.ReleaseDB() result, err := getAllVoters(db) if err != nil { diff --git a/server/actions/votes.go b/server/actions/votes.go index 7b7f43c..11a4b8e 100644 --- a/server/actions/votes.go +++ b/server/actions/votes.go @@ -51,7 +51,6 @@ func CastVote(c *gin.Context) { var hash string db := database.GetDB() - defer database.ReleaseDB() // Validation section // Check that election is open for voting, that the user hasn't voted already, @@ -147,7 +146,6 @@ func GetVotes(c *gin.Context) { } db := database.GetDB() - defer database.ReleaseDB() var votes []database.Vote if err := db.Preload("Rankings").Find(&votes, "election_id = ?", electionId).Error; err != nil { @@ -201,7 +199,6 @@ func CountVotes(c *gin.Context) { c.String(http.StatusBadRequest, util.InvalidElection) return } - database.ReleaseDB() if !election.Finalized { c.String(http.StatusBadRequest, "Can't count votes of unfinalized election") return @@ -312,7 +309,6 @@ func CountVotesSchultze(c *gin.Context) { c.String(http.StatusBadRequest, util.InvalidElection) return } - database.ReleaseDB() if !election.Finalized { c.String(http.StatusBadRequest, "Can't count votes of unfinalized election") return @@ -412,7 +408,6 @@ func GetHashes(c *gin.Context) { // TODO: possibly add electionID to database for hashes, since it would be nice // to be able to filter by that and only allow fetching from finalized elections db := database.GetDB() - defer database.ReleaseDB() var hashes []database.VoteHash if err := db.Find(&hashes).Error; err != nil { @@ -440,7 +435,6 @@ func HasVoted(c *gin.Context) { user := c.GetString("user") db := database.GetDB() - defer database.ReleaseDB() if db.Find(&database.CastedVote{ElectionID: electionId, Email: user}).RowsAffected == 0 { c.String(http.StatusOK, "false") diff --git a/server/db/db.go b/server/db/db.go index 9379ad8..bfa211e 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -3,7 +3,6 @@ package db import ( "fmt" "os" - "sync" "gorm.io/driver/postgres" "gorm.io/gorm" @@ -12,7 +11,6 @@ import ( ) var db *gorm.DB -var m sync.Mutex func InitDB() { c := config.GetConfig() @@ -33,14 +31,9 @@ func InitDB() { } func GetDB() *gorm.DB { - m.Lock() return db } -func ReleaseDB() { - m.Unlock() -} - // ReorderRows shuffles the rows in the specified table // O(N log N) ? func ReorderRows(db *gorm.DB, table string) error { diff --git a/server/middleware/voting.go b/server/middleware/voting.go index 7701f55..c24d959 100644 --- a/server/middleware/voting.go +++ b/server/middleware/voting.go @@ -19,10 +19,8 @@ func AllowedToVote() gin.HandlerFunc { if db.Find(&database.ValidVoter{Email: user}).RowsAffected == 0 { c.String(http.StatusForbidden, "Not registered as a valid voter") c.Abort() - database.ReleaseDB() return } - database.ReleaseDB() c.Next() } } From 631adca5345d6f77e2d1117f038931fb3183ccd9 Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Wed, 18 Oct 2023 17:10:00 +0200 Subject: [PATCH 11/15] Stop reordering rows on votes to make it go brrrrr --- server/actions/votes.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/actions/votes.go b/server/actions/votes.go index 11a4b8e..f0f07df 100644 --- a/server/actions/votes.go +++ b/server/actions/votes.go @@ -125,12 +125,12 @@ func CastVote(c *gin.Context) { // by correlating positions in the database tables. // Raises the time complexity of the vote operation a lot, but should be // fine for the amount of traffic expected for this system. - if err := database.ReorderRows(db, "casted_votes"); err != nil { - fmt.Println("Database failed to shuffle table casted_votes") - } - if err := database.ReorderRows(db, "vote_hashes"); err != nil { - fmt.Println("Database failed to shuffle table vote_hashes") - } + // if err := database.ReorderRows(db, "casted_votes"); err != nil { + // fmt.Println("Database failed to shuffle table casted_votes") + // } + // if err := database.ReorderRows(db, "vote_hashes"); err != nil { + // fmt.Println("Database failed to shuffle table vote_hashes") + // } c.String(http.StatusOK, hash) } From e84450da1974034be0fa00508236bfca5ff2b1aa Mon Sep 17 00:00:00 2001 From: Herman Karlsson Date: Mon, 4 Dec 2023 21:37:26 +0100 Subject: [PATCH 12/15] add back sidebar in admin menu --- client/src/components/AdminElectionView.tsx | 14 ++------------ server/actions/elections.go | 3 +++ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/client/src/components/AdminElectionView.tsx b/client/src/components/AdminElectionView.tsx index 06de946..92d64f6 100644 --- a/client/src/components/AdminElectionView.tsx +++ b/client/src/components/AdminElectionView.tsx @@ -82,7 +82,7 @@ export const AdminElectionView: React.FC = ({
- Alternativ att välja + Antal alternativ att välja = ({ />
-
- - Sekundära alternativ att välja - - -
-
- Antal lagda röster + Antal röster {submittedVotes ?? "-"} diff --git a/server/actions/elections.go b/server/actions/elections.go index a1d6e7b..a1975ba 100644 --- a/server/actions/elections.go +++ b/server/actions/elections.go @@ -152,6 +152,9 @@ func EditElection(c *gin.Context) { if body.Description != nil { election.Description = *body.Description } + if body.Mandates != nil { + election.Mandates = *body.Mandates + } if body.OpenTime != nil { election.OpenTime = util.ConvertNullTime(*body.OpenTime) } From 4cb30794b82d5b505c7edf3a933c886f87ac9769 Mon Sep 17 00:00:00 2001 From: Herman Karlsson Date: Mon, 4 Dec 2023 21:37:46 +0100 Subject: [PATCH 13/15] update stylin gand text and do some translations --- client/src/components/Voting.tsx | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/client/src/components/Voting.tsx b/client/src/components/Voting.tsx index 18d7927..65124d9 100644 --- a/client/src/components/Voting.tsx +++ b/client/src/components/Voting.tsx @@ -101,7 +101,9 @@ const useStyles = createStyles((theme) => ({ info : { padding: "1rem", + paddingBottom: "0.5rem", marginBottom: "1rem", + marginTop: "2rem", borderRadius: "0.5rem", backgroundColor: "rgb(197, 202, 233)", }, @@ -138,8 +140,9 @@ const InfoBox: React.FC = () => { return

- Sätt det alternativ du vill rösta på överst.

- Rangordningen på alla alternativ tas i beaktning. + Sätt det alternativ du vill rösta på överst.
+ Notera att ordningen på alla alternativ tas i beaktning.
+ Det går att ändra sin röst.

} @@ -285,7 +288,7 @@ export const Voting: React.FC = ({ )) return <> -
+

{election.description}

@@ -344,8 +347,7 @@ export const Voting: React.FC = ({ {!disabled && !loadingHasVoted && hasVoted &&

- Du har röstat i denna omröstning redan, men det går att ändra sin röst om det behövs.
- Notera att ordningen på alla alternativ spelar roll. + Du har röstat i den här omröstning redan, men det går att ändra sin röst om det behövs.

} @@ -370,9 +372,12 @@ export const Voting: React.FC = ({ */} - window.location.assign("/")} centered title="Vote successfully submitted"> + window.location.assign("/")} centered + title="Röst mottagen" + > - Your vote has successfully been submitted! You voted in the following order: + Du röstade med följande rankning
{submittedVoteOrder.map((candidate, i) => ( @@ -382,7 +387,7 @@ export const Voting: React.FC = ({ ))}
From 3cdef79a583c6c30019d91d13f2cd42bea2ea463 Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Mon, 18 Nov 2024 21:28:33 +0100 Subject: [PATCH 14/15] mkFix --- client/src/components/Authentication.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Authentication.tsx b/client/src/components/Authentication.tsx index d09625e..67b2551 100644 --- a/client/src/components/Authentication.tsx +++ b/client/src/components/Authentication.tsx @@ -7,7 +7,7 @@ import { } from "react-router-dom"; export const Login: React.FC = () => { - const loginURL = "https://login.datasektionen.se" + const loginURL = "https://logout.datasektionen.se/legacyapi" const callback = encodeURIComponent(`${window.location.origin}/#/token/`) const url = `${loginURL}/login?callback=${callback}`; From b046241f133e3688dc614a598d0f892b822aa036 Mon Sep 17 00:00:00 2001 From: Adrian Salamon Date: Sat, 7 Dec 2024 12:56:52 +0100 Subject: [PATCH 15/15] fix logout link --- client/src/components/Authentication.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Authentication.tsx b/client/src/components/Authentication.tsx index 67b2551..d9bdfe7 100644 --- a/client/src/components/Authentication.tsx +++ b/client/src/components/Authentication.tsx @@ -7,7 +7,7 @@ import { } from "react-router-dom"; export const Login: React.FC = () => { - const loginURL = "https://logout.datasektionen.se/legacyapi" + const loginURL = "https://sso.datasektionen.se/legacyapi" const callback = encodeURIComponent(`${window.location.origin}/#/token/`) const url = `${loginURL}/login?callback=${callback}`;