import {Candidate, Grade, Vote} from './type'; import {URL_SERVER} from './constants'; export const api = { routesServer: { setElection: 'elections', getElection: 'elections/:slug', getResults: 'results/:slug', voteElection: 'ballots', }, }; export interface GradePayload { name: string; description: string; id: number; value: number; } export interface CandidatePayload { name: string; description: string; id: number; image: string; } export interface ErrorMessage { loc: Array; msg: string; type: string; ctx: any; } export interface ErrorPayload { detail: Array; } export interface HTTPPayload { status: number; message: string; } export interface ElectionPayload { name: string; description: string; ref: string; date_start: string; date_end: string; hide_results: boolean; force_close: boolean; restricted: boolean; grades: Array; candidates: Array; } export interface ElectionCreatedPayload extends ElectionPayload { invites: Array; admin: string; num_voters: number; } export interface ElectionUpdatedPayload extends ElectionPayload { invites: Array; num_voters: number; status?: number; } export interface ResultsPayload extends ElectionPayload { status: number; ranking: {[key: string]: number}; merit_profile: {[key: number]: Array}; } export interface VotePayload { id: string; candidate: CandidatePayload; grade: GradePayload; } export interface BallotPayload { votes: Array; election: ElectionPayload; token: string; } export const createElection = async ( name: string, candidates: Array, grades: Array, description: string, numVoters: number, hideResults: boolean, forceClose: boolean, restricted: boolean, randomOrder: boolean, successCallback: Function = null, failureCallback: Function = console.log ) => { /** * Create an election from its title, its candidates and a bunch of options */ const endpoint = new URL(api.routesServer.setElection, URL_SERVER); if (!restricted && numVoters > 0) { throw new Error('Set the election as not restricted!'); } try { const req = await fetch(endpoint.href, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name, description: JSON.stringify({ description: description, randomOrder: randomOrder, }), candidates, grades, num_voters: numVoters, hide_results: hideResults, force_close: forceClose, restricted, }), }); if (req.ok && req.status === 200) { if (successCallback) { const payload = await req.json(); successCallback(payload); return payload; } } else if (failureCallback) { try { const payload = await req.json(); failureCallback(payload); } catch (e) { failureCallback(req.statusText); } } } catch (e) { return failureCallback && failureCallback(e); } }; export const updateElection = async ( ref: string, name: string, candidates: Array, grades: Array, description: string, numVoters: number, hideResults: boolean, forceClose: boolean, restricted: boolean, randomOrder: boolean, token: string ): Promise => { /** * Create an election from its title, its candidates and a bunch of options */ const endpoint = new URL(api.routesServer.setElection, URL_SERVER); try { const req = await fetch(endpoint.href, { method: 'PUT', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify({ ref, name, description: JSON.stringify({ description: description, randomOrder: randomOrder, }), candidates, grades, num_voters: numVoters, hide_results: hideResults, force_close: forceClose, restricted, }), }); if (!req.ok || req.status !== 200) { const payload = await req.json(); return {status: req.status, ...payload}; } const payload = await req.json(); return {status: 200, ...payload}; } catch (e) { console.error(e); return {status: 400, message: `Unknown API error: ${e}`}; } }; /** * Fetch results from external API */ export const getResults = async ( pid: string ): Promise => { const endpoint = new URL( api.routesServer.getResults.replace(new RegExp(':slug', 'g'), pid), URL_SERVER ); try { const response = await fetch(endpoint.href); if (response.status != 200) { const payload = await response.json(); return {status: response.status, ...payload}; } const payload = await response.json(); return {...payload, status: response.status}; } catch (error) { console.error(error); return {status: 400, message: `Unknown API error: ${error}`}; } }; export const getElection = async ( pid: string ): Promise => { /** * Fetch data from external API */ const path = api.routesServer.getElection.replace( new RegExp(':slug', 'g'), pid ); const endpoint = new URL(path, URL_SERVER); try { const response = await fetch(endpoint.href); if (response.status != 200) { const payload = await response.json(); return {status: response.status, message: payload}; } const payload = await response.json(); return {...payload, status: response.status}; } catch (error) { return {status: 400, message: 'Unknown API error'}; } }; /** * Fetch a ballot from the API. Return null if the ballot does not exist. */ export const getBallot = async ( token: string ): Promise => { const path = api.routesServer.voteElection const endpoint = new URL(path, URL_SERVER); if (!token) { throw new Error('Missing token'); } try { const response = await fetch(endpoint.href, { method: 'GET', headers: { Authorization: `Bearer ${token}`, }, }); if (response.status != 200) { return {status: response.status, message: "Can not load this ballot"}; } const payload = await response.json(); return {...payload, status: response.status}; } catch (error) { return {status: 400, message: 'Unknown API error'}; } }; export const castBallot = ( votes: Array, election: ElectionPayload, token?: string ) => { /** * Save a ballot on the remote database */ const endpoint = new URL(api.routesServer.voteElection, URL_SERVER); const payload = { election_ref: election.ref, votes: votes.map((v) => ({ candidate_id: election.candidates[v.candidateId].id, grade_id: election.grades[v.gradeId].id, })), }; if (!election.restricted) { return fetch(endpoint.href, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload), }); } else { if (!token) { throw new Error('Missing token'); } return fetch(endpoint.href, { method: 'PUT', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify(payload), }); } }; export const UNKNOWN_ELECTION_ERROR = 'E1:'; export const ONGOING_ELECTION_ERROR = 'E2:'; export const NO_VOTE_ERROR = 'E3:'; export const ELECTION_NOT_STARTED_ERROR = 'E4:'; export const ELECTION_FINISHED_ERROR = 'E5:'; export const INVITATION_ONLY_ERROR = 'E6:'; export const UNKNOWN_TOKEN_ERROR = 'E7:'; export const USED_TOKEN_ERROR = 'E8:'; export const WRONG_ELECTION_ERROR = 'E9:'; export const API_ERRORS = [ UNKNOWN_TOKEN_ERROR, ONGOING_ELECTION_ERROR, NO_VOTE_ERROR, ELECTION_NOT_STARTED_ERROR, ELECTION_FINISHED_ERROR, INVITATION_ONLY_ERROR, UNKNOWN_TOKEN_ERROR, USED_TOKEN_ERROR, WRONG_ELECTION_ERROR, ]; export const apiErrors = (error: string): string => { const errorCode = `${error.split(':')[0]}:`; if (API_ERRORS.includes(errorCode)) { return `error.${error.split(':')[0].toLowerCase()}`; } else { return 'error.catch22'; } };