Last active
March 9, 2024 05:28
-
-
Save helmturner/8cd1f67d54506f03f7e95e8c28bbf519 to your computer and use it in GitHub Desktop.
Tulsa WebDevs Voting API Spec
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# What is my vote if I don't vote at all? Is it a `null` vote, or is 0 the | |
# automatic default vote for non-voters? | |
# If `null` vote is the default: After I vote, but before the deadline, I can | |
# change my vote—including changing it to 0. Can I un-vote by setting it to `null`? | |
# Are vote windows limited in duration? i.e. do questions have an expiration | |
# (or vote-by) date? Or, is it more of a 'ranking' system where vote windows are | |
# unlimited in duration and a 'snapshot' of the votes is taken at some point | |
# (or multiple points) in time? | |
openapi: 3.0.0 | |
info: | |
title: Voting and Proposal API | |
description: API for casting votes on various topics/projects and submitting and managing proposals with a ranking and voting system. | |
version: 2.0.0 | |
servers: | |
- url: http://api.example.com/v1 | |
description: Main production server | |
paths: | |
/topics: | |
get: | |
summary: "Get a paginated list of all topics" | |
parameters: | |
- name: cursor | |
in: query | |
required: false | |
schema: | |
type: string | |
format: uuid | |
description: "Cursor for paginating through a list of votes" | |
- name: limit | |
in: query | |
required: false | |
schema: | |
default: 10 | |
type: integer | |
format: int32 | |
description: "Maximum number of votes to return" | |
responses: | |
'200': | |
description: "OK" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Paginated' | |
- type: object | |
required: | |
- list | |
properties: | |
list: | |
type: array | |
items: | |
allOf: | |
- $ref: '#/components/schemas/Topic' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'404': | |
description: "Not Found" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/404Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
post: | |
summary: "Create a new topic" | |
parameters: | |
- name: Authorization | |
in: header | |
required: true | |
schema: | |
type: string | |
format: uuid | |
requestBody: | |
required: true | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Topic' | |
- $ref: '#/components/schemas/Authored' | |
responses: | |
'201': | |
description: "Created" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Topic' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'401': | |
description: "Unauthorized" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/401Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
/topics/{id}: | |
summary: "Get, update, or delete a specific topic by ID" | |
parameters: | |
- name: id | |
in: path | |
required: true | |
schema: | |
type: string | |
format: uuid | |
description: "ID of the topic to get, update, or delete" | |
get: | |
summary: "Get a specific topic by ID" | |
responses: | |
'200': | |
description: "OK" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Topic' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'404': | |
description: "Not Found" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/404Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
post: | |
summary: "Update a specific topic by ID" | |
parameters: | |
- name: Authorization | |
in: header | |
required: true | |
schema: | |
type: string | |
format: uuid | |
requestBody: | |
required: true | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/InitiativeData' | |
- $ref: '#/components/schemas/DatabaseObject' | |
responses: | |
'200': | |
description: "OK" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Topic' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'401': | |
description: "Unauthorized" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/401Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
delete: | |
summary: "Delete a specific topic by ID" | |
parameters: | |
- name: Authorization | |
in: header | |
required: true | |
schema: | |
type: string | |
format: uuid | |
responses: | |
'204': | |
description: "No Content" | |
'401': | |
description: "Unauthorized" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/401Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
/topics/proposals: | |
get: | |
summary: "Get a paginated list of all proposals for topics" | |
parameters: | |
- name: cursor | |
in: query | |
required: false | |
schema: | |
type: string | |
format: uuid | |
description: "Cursor for paginating through a list of votes" | |
- name: limit | |
in: query | |
required: false | |
schema: | |
default: 10 | |
type: integer | |
format: int32 | |
description: "Maximum number of votes to return" | |
responses: | |
'200': | |
description: "OK" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Paginated' | |
- type: object | |
required: | |
- list | |
properties: | |
list: | |
type: array | |
items: | |
allOf: | |
- $ref: '#/components/schemas/Topic' | |
- $ref: '#/components/schemas/Proposal' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'404': | |
description: "Not Found" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/404Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
post: | |
summary: "Create a new proposal for a topic" | |
parameters: | |
- name: Authorization | |
in: header | |
required: true | |
schema: | |
type: string | |
format: uuid | |
requestBody: | |
required: true | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Topic' | |
- $ref: '#/components/schemas/Proposal' | |
- $ref: '#/components/schemas/Authored' | |
responses: | |
'201': | |
description: "Created" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Topic' | |
- $ref: '#/components/schemas/Proposal' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'401': | |
description: "Unauthorized" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/401Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
/projects: | |
get: | |
summary: "Get a paginated list of all projects" | |
parameters: | |
- name: cursor | |
in: query | |
required: false | |
schema: | |
type: string | |
format: uuid | |
description: "Cursor for paginating through a list of votes" | |
- name: limit | |
in: query | |
required: false | |
schema: | |
default: 10 | |
type: integer | |
format: int32 | |
description: "Maximum number of votes to return" | |
responses: | |
'200': | |
description: "OK" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Paginated' | |
- type: object | |
required: | |
- list | |
properties: | |
list: | |
type: array | |
items: | |
allOf: | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'404': | |
description: "Not Found" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/404Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
post: | |
summary: "Create a new project" | |
parameters: | |
- name: Authorization | |
in: header | |
required: true | |
schema: | |
type: string | |
format: uuid | |
requestBody: | |
required: true | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/Authored' | |
responses: | |
'201': | |
description: "Created" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'401': | |
description: "Unauthorized" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/401Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
/projects/{id}: | |
summary: "Get, update, or delete a specific project by ID" | |
parameters: | |
- name: id | |
in: path | |
required: true | |
schema: | |
type: string | |
format: uuid | |
description: "ID of the project to get, update, or delete" | |
get: | |
summary: "Get a specific project by ID" | |
responses: | |
'200': | |
description: "OK" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'404': | |
description: "Not Found" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/404Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
post: | |
summary: "Update a specific project by ID" | |
parameters: | |
- name: Authorization | |
in: header | |
required: true | |
schema: | |
type: string | |
format: uuid | |
requestBody: | |
required: true | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/DatabaseObject' | |
responses: | |
'200': | |
description: "OK" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'401': | |
description: "Unauthorized" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/401Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
delete: | |
summary: "Delete a specific project by ID" | |
parameters: | |
- name: Authorization | |
in: header | |
required: true | |
schema: | |
type: string | |
format: uuid | |
responses: | |
'204': | |
description: "No Content" | |
'401': | |
description: "Unauthorized" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/401Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
/projects/proposals: | |
get: | |
summary: "Get a paginated list of all proposals for projects" | |
parameters: | |
- name: cursor | |
in: query | |
required: false | |
schema: | |
type: string | |
format: uuid | |
description: "Cursor for paginating through a list of votes" | |
- name: limit | |
in: query | |
required: false | |
schema: | |
default: 10 | |
type: integer | |
format: int32 | |
description: "Maximum number of votes to return" | |
responses: | |
'200': | |
description: "OK" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Paginated' | |
- type: object | |
required: | |
- list | |
properties: | |
list: | |
type: array | |
items: | |
allOf: | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/Proposal' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'404': | |
description: "Not Found" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/404Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
post: | |
summary: "Create a new proposal for a project" | |
parameters: | |
- name: Authorization | |
in: header | |
required: true | |
schema: | |
type: string | |
format: uuid | |
requestBody: | |
required: true | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/Proposal' | |
- $ref: '#/components/schemas/Authored' | |
responses: | |
'201': | |
description: "Created" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/Proposal' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'401': | |
description: "Unauthorized" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/401Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
/proposals: | |
get: | |
summary: "Get a paginated list of all proposals" | |
parameters: | |
- name: cursor | |
in: query | |
required: false | |
schema: | |
type: string | |
format: uuid | |
description: "Cursor for paginating through a list of votes" | |
- name: limit | |
in: query | |
required: false | |
schema: | |
default: 10 | |
type: integer | |
format: int32 | |
description: "Maximum number of votes to return" | |
responses: | |
'200': | |
description: "OK" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Paginated' | |
- type: object | |
required: | |
- list | |
properties: | |
list: | |
type: array | |
items: | |
allOf: | |
- oneOf: | |
- $ref: '#/components/schemas/Topic' | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/Proposal' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'404': | |
description: "Not Found" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/404Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
post: | |
summary: "Create a new proposal" | |
parameters: | |
- name: Authorization | |
in: header | |
required: true | |
schema: | |
type: string | |
format: uuid | |
requestBody: | |
required: true | |
content: | |
application/json: | |
schema: | |
allOf: | |
- oneOf: | |
- $ref: '#/components/schemas/Topic' | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/Proposal' | |
- $ref: '#/components/schemas/Authored' | |
- type: object | |
required: | |
- type | |
responses: | |
'201': | |
description: "Created" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- oneOf: | |
- $ref: '#/components/schemas/Topic' | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/Proposal' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'401': | |
description: "Unauthorized" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/401Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
/proposals/{id}: | |
summary: "Get, update, or delete a specific proposal by ID" | |
parameters: | |
- name: id | |
in: path | |
required: true | |
schema: | |
type: string | |
format: uuid | |
description: "ID of the proposal to get, update, or delete" | |
get: | |
summary: "Get a specific proposal by ID" | |
responses: | |
'200': | |
description: "OK" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- oneOf: | |
- $ref: '#/components/schemas/Topic' | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/Proposal' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'404': | |
description: "Not Found" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/404Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
post: | |
summary: "Update a specific proposal by ID" | |
parameters: | |
- name: Authorization | |
in: header | |
required: true | |
schema: | |
type: string | |
format: uuid | |
requestBody: | |
required: true | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/DatabaseObject' | |
- oneOf: | |
- $ref: '#/components/schemas/Proposal' | |
- $ref: '#/components/schemas/InitiativeData' | |
responses: | |
'200': | |
description: "OK" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- oneOf: | |
- $ref: '#/components/schemas/Topic' | |
- $ref: '#/components/schemas/Project' | |
- $ref: '#/components/schemas/Proposal' | |
- $ref: '#/components/schemas/DatabaseObject' | |
- $ref: '#/components/schemas/TimeStamped' | |
'401': | |
description: "Unauthorized" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/401Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
delete: | |
summary: "Delete a specific proposal by ID" | |
parameters: | |
- name: Authorization | |
in: header | |
required: true | |
schema: | |
type: string | |
format: uuid | |
responses: | |
'204': | |
description: "No Content" | |
'401': | |
description: "Unauthorized" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/401Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
/proposals/{id}/votes: | |
summary: "Get, update, or delete votes for a specific proposal by ID" | |
parameters: | |
- name: id | |
in: path | |
required: true | |
schema: | |
type: string | |
format: uuid | |
description: "ID of the proposal to get, update, or delete votes for" | |
get: | |
summary: "Get the vote totals for a specific proposal by ID. If a cursor is provided, a paginated list of vote details will be returned." | |
parameters: | |
- name: cursor | |
in: query | |
required: false | |
schema: | |
type: string | |
format: uuid | |
description: "Cursor for paginating through a list of votes" | |
- name: limit | |
in: query | |
required: false | |
schema: | |
type: integer | |
format: int32 | |
description: "Maximum number of votes to return" | |
responses: | |
'200': | |
description: "OK" | |
content: | |
application/json: | |
schema: | |
allOf: | |
- type: object | |
required: | |
- tally | |
properties: | |
tally: | |
description: "Total tally of all votes, including those not returned in the response" | |
type: integer | |
format: int32 | |
votes: | |
allOf: | |
- $ref: '#/components/schemas/Paginated' | |
- type: object | |
required: | |
- list | |
properties: | |
list: | |
type: array | |
items: | |
$ref: '#/components/schemas/Vote' | |
'404': | |
description: "Not Found" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/404Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
post: | |
summary: "Create or update a vote for a proposal" | |
parameters: | |
- name: Authorization | |
in: header | |
required: true | |
schema: | |
type: string | |
format: uuid | |
requestBody: | |
required: true | |
content: | |
application/json: | |
schema: | |
allOf: | |
- $ref: '#/components/schemas/Vote' | |
- $ref: '#/components/schemas/Authored' | |
responses: | |
'201': | |
description: "Created" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Vote' | |
'401': | |
description: "Unauthorized" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/401Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
delete: | |
summary: "Delete a specific vote by ID" | |
parameters: | |
- name: Authorization | |
in: header | |
required: true | |
schema: | |
type: string | |
format: uuid | |
responses: | |
'204': | |
description: "No Content" | |
'401': | |
description: "Unauthorized" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/401Error' | |
default: | |
description: "Unexpected error" | |
content: | |
application/json: | |
schema: | |
$ref: '#/components/schemas/Error' | |
components: | |
schemas: | |
DatabaseObject: | |
type: object | |
required: | |
- id | |
properties: | |
id: | |
type: string | |
format: uuid | |
TimeStamped: | |
type: object | |
required: | |
- created | |
- updated | |
properties: | |
created: | |
type: string | |
format: date-time | |
updated: | |
type: string | |
format: date-time | |
InitiativeData: | |
type: object | |
properties: | |
title: | |
type: string | |
maxLength: 48 | |
summary: | |
type: string | |
maxLength: 255 | |
description: | |
type: string | |
Topic: | |
allOf: | |
- $ref: '#/components/schemas/InitiativeData' | |
- type: object | |
required: | |
- title | |
- summary | |
- description | |
properties: | |
type: | |
type: string | |
enum: | |
- topic | |
Project: | |
allOf: | |
- $ref: '#/components/schemas/InitiativeData' | |
- type: object | |
required: | |
- title | |
- summary | |
- description | |
properties: | |
type: | |
type: string | |
enum: | |
- project | |
Proposal: | |
type: object | |
required: | |
- status | |
properties: | |
status: | |
type: string | |
enum: | |
- draft | |
- rfc | |
- open | |
- closed | |
Vote: | |
type: object | |
required: | |
- initiativeId | |
- vote | |
properties: | |
initiativeId: | |
type: string | |
format: uuid | |
vote: | |
description: "Ranking values: -2 (strongly disinterested), -1 (slightly disinterested), 0 (neutral), 1 (slightly interested), 2 (strongly interested)" | |
type: string | |
enum: | |
- "-2" | |
- "-1" | |
- "0" | |
- "1" | |
- "2" | |
comment: | |
type: string | |
maxLength: 255 | |
Authored: | |
type: object | |
required: | |
- authorId | |
- authorEmail | |
properties: | |
authorId: | |
type: string | |
format: uuid | |
authorName: | |
type: string | |
maxLength: 48 | |
authorEmail: | |
type: string | |
Expirable: | |
type: object | |
required: | |
- expires | |
properties: | |
expires: | |
type: string | |
format: date-time | |
Paginated: | |
type: object | |
required: | |
- cursor | |
properties: | |
cursor: | |
type: string | |
format: uuid | |
total: | |
description: "Total number of items of this type in the database, if known" | |
type: integer | |
format: int32 | |
Error: | |
type: object | |
required: | |
- message | |
properties: | |
message: | |
type: string | |
404Error: | |
allOf: | |
- $ref: '#/components/schemas/Error' | |
- type: object | |
properties: | |
message: | |
type: string | |
default: "Not Found" | |
401Error: | |
allOf: | |
- $ref: '#/components/schemas/Error' | |
- type: object | |
properties: | |
message: | |
type: string | |
default: "Unauthorized" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment