Alexis Bruneteau 4c239c4552 feat: Add missing votes API proxy routes for blockchain queries
Created proxy routes to expose blockchain-related endpoints:
- GET /api/votes/public-keys - Get ElGamal public keys for vote encryption
- GET /api/votes/blockchain - Get blockchain state for an election
- GET /api/votes/results - Get election results from blockchain
- GET /api/votes/transaction-status - Check vote confirmation status

These routes forward requests to the backend and are required for the
frontend to access blockchain features like vote verification and
transaction status tracking.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 16:26:32 +01:00

280 lines
6.2 KiB
TypeScript

/**
* API Client for E-Voting Backend
* Handles all communication with the FastAPI backend
*/
// Use relative paths to go through Next.js proxy routes
// which handle the conversion between camelCase (frontend) and snake_case (backend)
const API_URL = ""
export interface ApiResponse<T> {
data?: T
error?: string
status: number
}
export interface AuthToken {
access_token: string
expires_in: number
id: number
email: string
first_name: string
last_name: string
}
export interface VoterProfile {
id: number
email: string
first_name: string
last_name: string
has_voted: boolean
created_at: string
}
export interface Election {
id: number
name: string
description: string
start_date: string
end_date: string
is_active: boolean
results_published: boolean
candidates: Candidate[]
}
export interface Candidate {
id: number
name: string
description: string
election_id: number
}
export interface Vote {
id: number
election_id: number
candidate_id: number
voter_id: number
ballot_hash: string
timestamp: string
}
export interface VoteHistory {
vote_id: number
election_id: number
election_name: string
candidate_name: string
vote_date: string
election_status: "active" | "closed" | "upcoming"
}
/**
* Get stored authentication token from localStorage
*/
export function getAuthToken(): string | null {
if (typeof window === "undefined") return null
return localStorage.getItem("auth_token")
}
/**
* Store authentication token in localStorage
*/
export function setAuthToken(token: string): void {
if (typeof window !== "undefined") {
localStorage.setItem("auth_token", token)
}
}
/**
* Remove authentication token from localStorage
*/
export function clearAuthToken(): void {
if (typeof window !== "undefined") {
localStorage.removeItem("auth_token")
}
}
/**
* Make API request with authentication
*/
async function apiRequest<T>(
endpoint: string,
options: RequestInit & { skipAuth?: boolean } = {}
): Promise<ApiResponse<T>> {
const { skipAuth = false, ...fetchOptions } = options
try {
const headers: Record<string, string> = {
"Content-Type": "application/json",
...((fetchOptions.headers as Record<string, string>) || {}),
}
// Add authentication token if available
if (!skipAuth) {
const token = getAuthToken()
if (token) {
headers["Authorization"] = `Bearer ${token}`
}
}
const response = await fetch(`${API_URL}${endpoint}`, {
...fetchOptions,
headers,
})
if (response.status === 401) {
// Token expired or invalid
clearAuthToken()
throw new Error("Unauthorized - please login again")
}
if (!response.ok) {
const error = await response.json().catch(() => ({}))
throw new Error(error.detail || `HTTP ${response.status}`)
}
const data = await response.json()
return { data, status: response.status }
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error"
return { error: message, status: 500 }
}
}
/**
* Authentication APIs
*/
export const authApi = {
async register(email: string, password: string, firstName: string, lastName: string, citizenId: string) {
return apiRequest<AuthToken>("/api/auth/register", {
method: "POST",
skipAuth: true,
body: JSON.stringify({
email,
password,
firstName,
lastName,
citizenId,
}),
})
},
async login(email: string, password: string) {
return apiRequest<AuthToken>("/api/auth/login", {
method: "POST",
skipAuth: true,
body: JSON.stringify({ email, password }),
})
},
async getProfile() {
return apiRequest<VoterProfile>("/api/auth/profile")
},
logout() {
clearAuthToken()
return { data: null, status: 200 }
},
}
/**
* Elections APIs
*/
export const electionsApi = {
async getActive() {
return apiRequest<Election[]>("/api/elections/active")
},
async getUpcoming() {
return apiRequest<Election[]>("/api/elections/upcoming")
},
async getCompleted() {
return apiRequest<Election[]>("/api/elections/completed")
},
async getById(id: number) {
return apiRequest<Election>(`/api/elections/${id}`)
},
async getCandidates(electionId: number) {
return apiRequest<Candidate[]>(`/api/elections/${electionId}/candidates`)
},
async getResults(electionId: number) {
return apiRequest<any>(`/api/elections/${electionId}/results`)
},
async publishResults(electionId: number) {
return apiRequest<any>(`/api/elections/${electionId}/publish-results`, {
method: "POST",
})
},
}
/**
* Votes APIs
*/
export const votesApi = {
async submitVote(electionId: number, choix: string) {
return apiRequest<Vote>("/api/votes", {
method: "POST",
body: JSON.stringify({
election_id: electionId,
choix: choix,
}),
})
},
async getStatus(electionId: number) {
return apiRequest<{ has_voted: boolean }>(`/api/votes/status?election_id=${electionId}`)
},
async getHistory() {
return apiRequest<VoteHistory[]>("/api/votes/history")
},
async getBlockchain(electionId: number) {
return apiRequest<any>(`/api/votes/blockchain?election_id=${electionId}`)
},
async getResults(electionId: number) {
return apiRequest<any>(`/api/votes/results?election_id=${electionId}`)
},
async verifyBlockchain(electionId: number) {
return apiRequest<any>("/api/votes/verify-blockchain", {
method: "POST",
body: JSON.stringify({ election_id: electionId }),
})
},
async getTransactionStatus(transactionId: string, electionId: number) {
return apiRequest<any>(`/api/votes/transaction-status?transaction_id=${transactionId}&election_id=${electionId}`)
},
async getPublicKeys(electionId: number) {
return apiRequest<any>(`/api/votes/public-keys?election_id=${electionId}`, {
skipAuth: true,
})
},
async setupElection(electionId: number) {
return apiRequest<any>("/api/votes/setup", {
method: "POST",
body: JSON.stringify({ election_id: electionId }),
})
},
}
/**
* Health check
*/
export async function healthCheck() {
try {
const response = await fetch(`${API_URL}/health`)
return response.ok
} catch {
return false
}
}