liquidvotingio / api

Enable liquid democracy in your decision making platform
GNU Affero General Public License v3.0
17 stars 3 forks source link

Liquid Voting as a Service

Actions Status

A liquid voting service that aims to be easily plugged into proposal-making platforms of different kinds. Learn more about the idea and motivation on this blog post.

In this repo there's an Elixir/Phoenix GraphQL API implementing the most basic liquid democracy concepts: participants, proposals, votes and delegations.

It's deployed on https://api.liquidvoting.io. See sample queries below, in Using the API.

There's a dockerized version of the API. The live API is running on Google Kubernetes Engine. The intention is to make the service easily deployable within a microservices/cloud native context.

You can follow the project backlog here.

The live API is getting ready to be used in production platforms. If you're interested, let us know so we can learn more about your project, and we'll provide you with an access key right away.

Concepts and modeling

Participants are users with a name and email, and they can vote on external content (say a blog post, or a pull request), identified as proposal urls, or delegate their votes to another Participant who can then vote for them, or delegate both votes to a third Participant, and so on.

Votes are yes/no booleans and reference a voter (a Participant) and a proposal_url, and Delegations are references to a delegator (a Participant) and a delegate (another Participant).

Once each vote is created, delegates' votes will have a different VotingWeight based on how many delegations they've received.

A VotingResult is calculated taking the votes and their different weights into account. This is a resource the API exposes as a possible subscription, for real-time updates over Phoenix Channels.

The syntax for subscribing, and for all other queries and mutations, can be seen following the setup instructions below.

Local setup

Building from the repo

You'll need Elixir 1.10, Phoenix 1.4.10 and Postgres 10 installed.

Clone the repo and:

mix deps.get
mix ecto.setup
mix phx.server

Running the dockerized version

Using docker-compose:

Clone the repo and:

$ docker-compose up

Running the container:

Mac OSX:

docker run -it --rm \
  -e SECRET_KEY_BASE=$(mix phx.gen.secret) \
  -e DB_USERNAME=postgres \
  -e DB_PASSWORD=postgres \
  -e DB_NAME=liquid_voting_dev \
  -e DB_HOST=host.docker.internal \
  -p 4000:4000 \
  ghcr.io/liquidvotingio/api:latest

Linux:

docker run -it --rm --network=host \
  -e SECRET_KEY_BASE=$(mix phx.gen.secret) \
  -e DB_USERNAME=postgres \
  -e DB_PASSWORD=postgres \
  -e DB_NAME=liquid_voting_dev \
  -e DB_HOST=127.0.0.1 \
  ghcr.io/liquidvotingio/api:latest

(assuming you already have the database up and running)

You can run migrations by passing an eval command to the containerized app, like this:

docker run -it --rm \
  <same options>
  ghcr.io/liquidvotingio/api:latest eval "LiquidVoting.Release.migrate"

Once you're up and running

Open a GraphiQL window in your browser at http://localhost:4000/graphiql, then configure an Org-ID header:

{ "Org-ID": "b7a9cae5-6e3a-48b1-8730-8b5c8d6c9b5a"}

You can then use the queries below to interact with the API.

Using the API

Create votes and delegations using GraphQL mutations

mutation {
  createVote(participantEmail: "jane@somedomain.com", proposalUrl:"https://github.com/user/repo/pulls/15", yes: true) {
    participant {
      email
    }
    yes
  }
}

mutation {
  createDelegation(proposalUrl: "https://github.com/user/repo/pulls/15", delegatorEmail: "nelson@somedomain.com", delegateEmail: "liz@somedomain.com") {
    delegator {
      email
    }
    delegate {
      email
    }
  }
}

mutation {
  createVote(participantEmail: "liz@somedomain.com", proposalUrl:"https://github.com/user/repo/pulls/15", yes: false) {
    participant {
      email
    }
    yes
    votingResult {
      inFavor
      against
    }
  }
}

Then run some queries, inserting valid id values where indicated:

query {
  participants {
    email
    id
    delegationsReceived {
      delegator {
        email
      }
      delegate {
        email
      }
    }
  }
}

query {
  participant(id: <participant id fetched in previous query>) {
    email
    delegationsReceived {
      delegator {
        email
      }
      delegate {
        email
      }
    }
  }
}

query {
  votes {
    yes
    weight
    proposalUrl
    id
    participant {
      email
    }
  }
}

query {
  vote(id: <vote id fetched in previous query>) {
    yes
    weight
    proposalUrl
    participant {
      email
    }
  }
}

query {
  delegations {
    id
    delegator {
      email
    }
    delegate {
      email
    }
  }
}

query {
  delegation(id: <delegation id fetched in previous query>) {
    delegator {
      email
    }
    delegate {
      email
    }
  }
}

query {
  votingResult(proposalUrl: "https://github.com/user/repo/pulls/15") {
    inFavor
    against
    proposalUrl
  }
}

And subscribe to voting results (which will react to voting creation):

subscription {
  votingResultChange(proposalUrl:"https://github.com/user/repo/pulls/15") {
    inFavor
    against
    proposalUrl
  }
}

To see this in action, open a second graphiql window in your browser and run createVote mutations there, and watch the subscription responses come through on the first one.

With the examples above, the inFavor count should be 1, and against should be 2 since liz@somedomain.com had a delegation from nelson@somedomain.com.