You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mvfront-react/services/api.ts

354 lines
8.2 KiB

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<string>;
msg: string;
type: string;
ctx: any;
}
export interface ErrorPayload {
detail: Array<ErrorMessage>;
}
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<GradePayload>;
candidates: Array<CandidatePayload>;
}
export interface ElectionCreatedPayload extends ElectionPayload {
invites: Array<string>;
admin: string;
num_voters: number;
}
export interface ElectionUpdatedPayload extends ElectionPayload {
invites: Array<string>;
num_voters: number;
status?: number;
}
export interface ResultsPayload extends ElectionPayload {
status: number;
ranking: {[key: string]: number};
merit_profile: {[key: number]: Array<number>};
}
export interface VotePayload {
id: string;
candidate: CandidatePayload;
grade: GradePayload;
}
export interface BallotPayload {
votes: Array<VotePayload>;
election: ElectionPayload;
token: string;
}
export const createElection = async (
name: string,
candidates: Array<Candidate>,
grades: Array<Grade>,
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<Candidate>,
grades: Array<Grade>,
description: string,
numVoters: number,
hideResults: boolean,
forceClose: boolean,
restricted: boolean,
randomOrder: boolean,
token: string
): Promise<ElectionUpdatedPayload | HTTPPayload> => {
/**
* 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<ResultsPayload | HTTPPayload> => {
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<ElectionPayload | HTTPPayload> => {
/**
* 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<ElectionPayload | HTTPPayload> => {
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<Vote>,
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';
}
};