Skip to content
This repository has been archived by the owner on Mar 11, 2024. It is now read-only.

Commit

Permalink
API and Resource implementation (#241)
Browse files Browse the repository at this point in the history
* Updates fetch to use API

* Removes unnecessary slash

* Moves the API service to use the adapter pattern

* Cleans up request

* Adds options to api

* Adds resource

* Removes async awaits
  • Loading branch information
eluciano11 committed Jan 24, 2021
1 parent 520a87a commit 603e747
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 121 deletions.
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { createMachine, assign } from "xstate"

import { FindYourCenterContext, FindYourCenterEvent } from "./types"
import { VoterInformationResource } from "../../../packages/practica/resource"

const isNumberExpr = new RegExp(/^\d+$/)

const getVoterDetails = (voterId?: string) =>
fetch(`https://api.paravotar.org/consulta?voterId=${voterId}`).then(
response => {
if (!response.ok) {
throw new Error("HTTP status code: " + response.status)
}
return response.json()
}
)
const getVoterDetails = (voterId?: string) => {
return VoterInformationResource.getVoterInfo(voterId)
}

export const findYourCenterMachine = createMachine<
FindYourCenterContext,
Expand Down
6 changes: 3 additions & 3 deletions src/packages/generate-ballot/machines/ballot-machine.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Machine, assign } from "xstate"

import { BallotResource } from "../../practica/resource"
import {
LegislativeBallotConfig,
MunicipalBallotConfig,
StateBallotConfig,
} from "../../practica/services/ballot-configs"

import { CDN_URL } from "../../practica/services/constants"
import { BallotMachineContext } from "../types/ballot-machine"

async function fetchBallot(path: string | null) {
if (path) {
const resp = await fetch(`${CDN_URL}${path}/data.json`)
const data = await resp.json()
const data = await BallotResource.getBallot(`${path}/`)

return data
}
Expand Down
35 changes: 35 additions & 0 deletions src/packages/practica/resource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import api from "../../services/api"
import { BallotsResponse, OcrResult, VoterInfo } from "./services/types"
import { CDN_URL } from "./services/constants"

export const VoterInformationResource = {
getVoterInfo(voterId?: string) {
return api.get<VoterInfo>(`/consulta?voterId=${voterId}`)
},
}

export const BallotResource = {
getBallotsByPrecint(precint: string) {
return api.get<BallotsResponse>(`/ballots/ByPrecint?precintId=${precint}`)
},

getBallotsByTown(town: string) {
return api.get<BallotsResponse>(`/ballots/ByTown?townId=${town}`)
},

async getBallot(path: string) {
return api.get<OcrResult[][]>(`/${path}data.json`, { baseUrl: CDN_URL })
},

createBallotPdf(ballot: {
ballotType: string
ballotPath: string
votes: string
}) {
return api.post("/createBallotTask", ballot)
},

getBallotPdf(params: string) {
api.get(`/getPdfUrl?${params}`)
},
}
87 changes: 13 additions & 74 deletions src/packages/practica/services/ballot-finder-service.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,17 @@
import { API_URL } from "./constants"

type VoterInfo = {
estatus: string
numeroElectoral: string
precinto: string
unidad: string
}

type BallotsResponse = {
estatal: string
municipal: string
legislative: string
}
import { BallotResource, VoterInformationResource } from "../resource"
import { BallotsResponse, VoterInfo } from "./types"

async function getBallotsByVoterId(voterId: string) {
const voterInfoRes = await fetch(`${API_URL}/consulta?voterId=${voterId}`)
const voterInfoJson: VoterInfo = await voterInfoRes.json()
const ballotsRes = await fetch(
`${API_URL}/ballots/ByPrecint?precintId=${voterInfoJson.precinto}`
const voterInfo: VoterInfo = await VoterInformationResource.getVoterInfo(
voterId
)
const ballots: BallotsResponse = await BallotResource.getBallotsByPrecint(
voterInfo.precinto
)
const ballotsJson: BallotsResponse = await ballotsRes.json()

return ballotsJson
return ballots
}

// // Prefetch ballot data
// const ballots = Object.entries(ballotsJson).map(async ([key, value]) => {
// try {
// const ballotRes = await fetch(`${PUBLIC_S3_BUCKET}/${value}data.json`)
// const ballotJson: OcrResult[][] = await ballotRes.json()

// if (key === "estatal") {
// return {
// [key]: new StateBallotConfig(ballotJson, ballotsJson.estatal),
// }
// } else if (key === "municipal") {
// return {
// [key]: new MunicipalBallotConfig(ballotJson, ballotsJson.municipal),
// }
// } else {
// return {
// [key]: new LegislativeBallotConfig(
// ballotJson,
// ballotsJson.legislativa
// ),
// }
// }
// } catch (err) {
// console.log(err)
// }
// })

// const allBallotsJson = await Promise.all(ballots)
// const initialValue: {
// estatal?: StateBallotConfig
// municipal?: MunicipalBallotConfig
// legislativa?: LegislativeBallotConfig
// } = {
// estatal: undefined,
// municipal: undefined,
// legislativa: undefined,
// }

// return allBallotsJson.reduce((prev, curr) => {
// return {
// ...prev,
// ...curr,
// }
// }, initialValue)

function prefixPrecint(precint: string) {
let input = precint
const inputSize = precint.length
Expand All @@ -89,21 +32,17 @@ function prefixPrecint(precint: string) {

async function getBallotsByPrecint(precint: string) {
const prefixedPrecint = prefixPrecint(precint)
const ballotsByPrecintRes = await fetch(
`${API_URL}/ballots/ByPrecint?precintId=${prefixedPrecint}`
const ballots: BallotsResponse = await BallotResource.getBallotsByPrecint(
prefixedPrecint
)
const ballotsJson: BallotsResponse = await ballotsByPrecintRes.json()

return ballotsJson
return ballots
}

async function getBallotsByTown(town: string) {
const ballotsByPrecintRes = await fetch(
`${API_URL}/ballots/ByTown?townId=${town}`
)
const ballotsJson: BallotsResponse = await ballotsByPrecintRes.json()
const ballots: BallotsResponse = await BallotResource.getBallotsByTown(town)

return ballotsJson
return ballots
}

export enum FindByType {
Expand Down
47 changes: 12 additions & 35 deletions src/packages/practica/services/ballot-service.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { stringify } from "qs"

import { BallotType, Selection } from "../../../ballot-validator/types"
import { VotesCoordinates } from "../../generate-ballot/types/ballot-machine"
import {
CandidateVoteStrategy,
MixedVoteStrategy,
PartyVoteStrategy,
VoteUpdateManager,
} from "../strategies"
import { BallotResource } from "../resource"
import {
BallotConfigs,
LegislativeBallotConfig,
MunicipalBallotConfig,
StateBallotConfig,
} from "./ballot-configs"
import { ElectiveField } from "./ballot-configs/base"
import { API_URL, CDN_URL } from "./constants"
import { OcrResult, PracticeContext } from "./types"
import { getExplicitlySelectedVotes, Vote } from "./vote-service"
import BallotFinder, { FindByType } from "./ballot-finder-service"
import {
CandidateVoteStrategy,
MixedVoteStrategy,
PartyVoteStrategy,
VoteUpdateManager,
} from "../strategies"

type FindByEventParams = {
userInput: string
Expand Down Expand Up @@ -65,8 +66,7 @@ const BallotService = {
legislativa: LegislativeBallotConfig
}> = Object.entries(ballotPaths).map(async ([key, value]) => {
try {
const ballotRes = await fetch(`${CDN_URL}/${value}data.json`)
const ballotJson: OcrResult[][] = await ballotRes.json()
const ballotJson: OcrResult[][] = await BallotResource.getBallot(value)

if (key === "estatal") {
return {
Expand Down Expand Up @@ -195,42 +195,19 @@ const BallotService = {
}
}
)
const votes = JSON.stringify(voteCoordinates)

const res = await fetch(`${API_URL}/createBallotTask`, {
method: "POST",
body: JSON.stringify({
ballotType: event.ballotType,
ballotPath: `/${event.ballotPath.substr(
0,
event.ballotPath.length - 1
)}`,
votes: votes,
}),
})
const result = await res.json()

const params = new URLSearchParams({
const result = await BallotResource.createBallotPdf({
ballotType: event.ballotType,
ballotPath: `/${event.ballotPath.substr(0, event.ballotPath.length - 1)}`,
votes: JSON.stringify(voteCoordinates),
})

params.append("votes", votes)
params.toString()

return result.uuid
},

async getPdfUrl(context: PracticeContext) {
const { uuid } = context
const params = stringify({ uuid })
const res = await fetch(`${API_URL}/getPdfUrl?${params}`)

if (!res.ok) {
throw new Error("Something went wrong")
}

const result = await res.json()
const result = await BallotResource.getBallotPdf(params)

return result
},
Expand Down
13 changes: 13 additions & 0 deletions src/packages/practica/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,16 @@ export type VoteEvent = {
ballotType: BallotType
ballot?: BallotConfigs
}

export type VoterInfo = {
estatus: string
numeroElectoral: string
precinto: string
unidad: string
}

export type BallotsResponse = {
estatal: string
municipal: string
legislative: string
}
79 changes: 79 additions & 0 deletions src/services/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { API_URL } from "../packages/practica/services/constants"

interface RequestOptions {
baseUrl?: string
}

interface ApiInterface {
get<T>(endpoint: string, options?: RequestOptions): Promise<T>
post<T>(endpoint: string, params: any, options?: RequestOptions): Promise<T>
}

class FetchAdapter implements ApiInterface {
private baseUrl

constructor(baseUrl: string) {
this.baseUrl = baseUrl
}

private async processResponse<T>(res: Response) {
if (!res.ok) {
throw Error("Network request failed.")
}

const data: T = await res.json()

return data
}

private getBaseUrl(options?: RequestOptions) {
if (options && options.baseUrl) {
return options.baseUrl
}

return this.baseUrl
}

async get<T>(endpoint: string, options?: RequestOptions) {
const baseUrl = this.getBaseUrl(options)
const res = await fetch(`${baseUrl}${endpoint}`)
const data = await this.processResponse<T>(res)

return data
}

async post(endpoint: string, params: any, options?: RequestOptions) {
const baseUrl = this.getBaseUrl(options)
const res = await fetch(`${baseUrl}${endpoint}`, {
method: "POST",
body: JSON.stringify(params),
})
const data = await this.processResponse<T>(res)

return data
}
}

class ApiService {
private adapter

constructor(adapter: ApiInterface) {
this.adapter = adapter
}

async get<T>(endpoint: string, options?: RequestOptions) {
const res = await this.adapter.get<T>(endpoint, options)

return res
}

async post(endpoint: string, params: any, options?: RequestOptions) {
const res = await this.adapter.post(endpoint, params, options)

return res
}
}

const adapter = new FetchAdapter(API_URL)

export default new ApiService(adapter)

0 comments on commit 603e747

Please sign in to comment.