From 1ce8bee5a8fe54c6d4f20fa5337f104fff99001c Mon Sep 17 00:00:00 2001 From: "R. Tyler Croy" Date: Sun, 20 Sep 2020 20:29:48 -0700 Subject: [PATCH] Add an OpenAPI description and some overrides for debug builds to test locally The CORS stuff allows for testing against the API with swagger-editor on my machine but is not strictly necessary. The `dredd.yml` is for use with Dredd (`npm i dredd`) and doesn't _currently_ pass muster --- api-description.yml | 210 ++++++++++++++++++++++++++++++++++++++++++++ dredd.yml | 32 +++++++ src/main.rs | 26 ++++++ 3 files changed, 268 insertions(+) create mode 100644 api-description.yml create mode 100644 dredd.yml diff --git a/api-description.yml b/api-description.yml new file mode 100644 index 0000000..4e50205 --- /dev/null +++ b/api-description.yml @@ -0,0 +1,210 @@ +--- +swagger: "2.0" +info: + description: | + Dot dot vote! + version: "1.0.0" + title: Dot dot vote + contact: + email: "rtyler+dotdotvote@brokenco.de" + license: + name: "LGPL v3.0" + url: "https://www.gnu.org/licenses/lgpl-3.0.en.html" +host: "localhost:8000" +basePath: "/api/v1" +tags: +- name: "poll" + description: Poll manipulation APIs + externalDocs: + description: "Find out more" + url: "http://swagger.io" +schemes: +- "http" +- "https" +paths: + /polls: + put: + tags: + - "poll" + summary: "Create a new poll" + description: "Create a new poll" + operationId: "createPoll" + consumes: + - "application/json" + produces: + - "application/json" + parameters: + - in: "body" + name: "body" + description: | + Insertable Poll object that must be provided in order to actually create the poll. + required: true + schema: + $ref: "#/definitions/InsertablePoll" + responses: + '201': + description: | + Poll created successfully + schema: + type: 'object' + properties: + poll: + type: string + description: 'A UUID for the generated poll' + example: '8497479a-9f07-4530-9a5c-2824238d5975' + "422": + description: | + Invalid JSON supplied + '500': + description: | + Some server side error has occurred. + '/polls/{uuid}': + get: + tags: + - poll + summary: 'Fetch the details of the given poll' + description: | + Access the Poll details and metadata, but _not_ the results + parameters: + - in: path + name: uuid + required: true + type: string + format: uuid + + responses: + 200: + description: Poll found + schema: + $ref: '#/definitions/PollResponse' + 400: + description: | + Either the UUID parameter wasn't provided or it did not parse as a legitimate UUIDv4 + 404: + description: | + Poll not found + '/polls/{uuid}/vote': + post: + tags: + - poll + summary: 'Vote in the specified poll' + parameters: + - in: path + name: uuid + required: true + type: string + format: uuid + responses: + 200: + description: Vote submitted + 400: + description: | + Either the UUID parameter wasn't provided or it did not parse as a legitimate UUIDv4 + 404: + description: | + Poll not found + '/polls/{uuid}/results': + get: + tags: + - poll + summary: 'Fetch the results for the specified poll' + parameters: + - in: path + name: uuid + required: true + type: string + format: uuid + responses: + 200: + description: Poll found + schema: + $ref: '#/definitions/PollResults' + 400: + description: | + Either the UUID parameter wasn't provided or it did not parse as a legitimate UUIDv4 + 404: + description: | + Poll not found +definitions: + PollResponse: + type: object + properties: + poll: + $ref: '#/definitions/RawPoll' + choices: + type: array + items: + $ref: '#/definitions/RawChoice' + PollResults: + type: object + properties: + poll: + $ref: '#/definitions/RawPoll' + choices: + type: array + items: + $ref: '#/definitions/RawChoice' + votes: + type: array + items: + $ref: '#/definitions/RawVote' + RawPoll: + type: object + properties: + id: + type: number + format: int32 + uuid: + type: string + format: uuid + title: + type: string + created_at: + type: string + format: date-time + RawChoice: + type: object + properties: + id: + type: number + format: int32 + poll_id: + type: number + format: int32 + details: + type: string + created_at: + type: string + format: date-time + RawVote: + type: object + properties: + id: + type: number + format: int32 + poll_id: + type: number + format: int32 + choice_id: + type: number + format: int32 + dots: + type: number + format: in32 + voter: + type: string + created_at: + type: string + format: date-time + + InsertablePoll: + type: "object" + properties: + title: + type: "string" + example: 'My amazing poll!' + choices: + type: "array" + items: + type: "string" + example: 'Choice 1' diff --git a/dredd.yml b/dredd.yml new file mode 100644 index 0000000..62af6af --- /dev/null +++ b/dredd.yml @@ -0,0 +1,32 @@ +color: true +header: true +dry-run: null +hookfiles: null +language: nodejs +require: null +server: cargo run +server-wait: 3 +init: false +custom: {} +names: false +only: [] +reporter: [] +output: [] +sorted: false +user: null +inline-errors: false +details: false +method: [] +loglevel: warning +path: [] +hooks-worker-timeout: 5000 +hooks-worker-connect-timeout: 1500 +hooks-worker-connect-retry: 500 +hooks-worker-after-connect-wait: 100 +hooks-worker-term-timeout: 5000 +hooks-worker-term-retry: 500 +hooks-worker-handler-host: 127.0.0.1 +hooks-worker-handler-port: 61321 +config: ./dredd.yml +blueprint: api-description.yml +endpoint: 'http://localhost:8000' diff --git a/src/main.rs b/src/main.rs index f1ecb9a..d12b87a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,18 @@ mod dao { } impl Poll { + /* + pub async fn create(title: &str, tx: &mut (impl sqlx::Connection + Copy + sqlx::executor::RefExecutor<'_>)) -> Result { + sqlx::query_as!(Poll, + "INSERT INTO polls (title, uuid) VALUES ($1, $2) RETURNING *", + title, + Uuid::new_v4() + ) + .fetch_one(tx) + .await + } + */ + pub async fn from_uuid(uuid: uuid::Uuid, db: &crate::DbPool) -> Result { sqlx::query_as!(Poll, "SELECT * FROM polls WHERE uuid = $1", uuid) .fetch_one(db) @@ -343,6 +355,20 @@ async fn main() -> Result<(), std::io::Error> { Ok(db) => { let state = AppState { db }; let mut app = tide::with_state(state); + + #[cfg(debug_assertions)] + { + info!("Enabling a very liberal CORS policy for debug purposes"); + use tide::security::{CorsMiddleware, Origin}; + let cors = CorsMiddleware::new() + .allow_methods("GET, POST, PUT, OPTIONS".parse::().unwrap()) + .allow_origin(Origin::from("*")) + .allow_credentials(false); + + app.with(cors); + } + + debug!("Configuring routes"); app.at("/").get(routes::index); app.at("/api/v1/polls").put(routes::polls::create); app.at("/api/v1/polls/:uuid").get(routes::polls::get);