mirror of
https://github.com/dat515-2025/Group-8.git
synced 2026-03-22 06:57:47 +01:00
242 lines
7.0 KiB
TypeScript
242 lines
7.0 KiB
TypeScript
import { BACKEND_URL } from './config';
|
|
|
|
export type LoginResponse = {
|
|
access_token: string;
|
|
token_type: string;
|
|
};
|
|
|
|
export type Category = {
|
|
id: number;
|
|
name: string;
|
|
description?: string | null;
|
|
};
|
|
|
|
export type Transaction = {
|
|
id: number;
|
|
amount: number;
|
|
description?: string | null;
|
|
category_ids: number[];
|
|
date?: string | null; // ISO date (YYYY-MM-DD)
|
|
};
|
|
|
|
export async function deleteTransaction(id: number): Promise<void> {
|
|
const res = await fetch(`${getBaseUrl()}/transactions/${id}/delete`, {
|
|
method: 'DELETE',
|
|
headers: getHeaders('none'),
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(text || 'Failed to delete transaction');
|
|
}
|
|
}
|
|
|
|
function getBaseUrl() {
|
|
const base = BACKEND_URL?.replace(/\/$/, '') || '';
|
|
return base || '';
|
|
}
|
|
|
|
function getHeaders(contentType: 'json' | 'form' | 'none' = 'json'): Record<string, string> {
|
|
const token = localStorage.getItem('token');
|
|
const headers: Record<string, string> = {};
|
|
|
|
if (contentType === 'json') {
|
|
headers['Content-Type'] = 'application/json';
|
|
} else if (contentType === 'form') {
|
|
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
}
|
|
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
export async function login(email: string, password: string): Promise<void> {
|
|
const body = new URLSearchParams();
|
|
body.set('username', email);
|
|
body.set('password', password);
|
|
|
|
const res = await fetch(`${getBaseUrl()}/auth/jwt/login`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: body.toString(),
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(text || 'Login failed');
|
|
}
|
|
const data: LoginResponse = await res.json();
|
|
localStorage.setItem('token', data.access_token);
|
|
}
|
|
|
|
export async function register(email: string, password: string, first_name?: string, last_name?: string): Promise<void> {
|
|
const res = await fetch(`${getBaseUrl()}/auth/register`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ email, password, first_name, last_name }),
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(text || 'Registration failed');
|
|
}
|
|
}
|
|
|
|
export async function getCategories(): Promise<Category[]> {
|
|
const res = await fetch(`${getBaseUrl()}/categories/`, {
|
|
headers: getHeaders(),
|
|
});
|
|
if (!res.ok) throw new Error('Failed to load categories');
|
|
return res.json();
|
|
}
|
|
|
|
export type CreateTransactionInput = {
|
|
amount: number;
|
|
description?: string;
|
|
category_ids?: number[];
|
|
date?: string; // YYYY-MM-DD
|
|
};
|
|
|
|
export async function createTransaction(input: CreateTransactionInput): Promise<Transaction> {
|
|
const res = await fetch(`${getBaseUrl()}/transactions/create`, {
|
|
method: 'POST',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify(input),
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(text || 'Failed to create transaction');
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export async function getTransactions(start_date?: string, end_date?: string): Promise<Transaction[]> {
|
|
const params = new URLSearchParams();
|
|
if (start_date) params.set('start_date', start_date);
|
|
if (end_date) params.set('end_date', end_date);
|
|
const qs = params.toString();
|
|
const url = `${getBaseUrl()}/transactions/${qs ? `?${qs}` : ''}`;
|
|
const res = await fetch(url, {
|
|
headers: getHeaders(),
|
|
});
|
|
if (!res.ok) throw new Error('Failed to load transactions');
|
|
return res.json();
|
|
}
|
|
|
|
export type User = {
|
|
id: string;
|
|
email: string;
|
|
first_name?: string | null;
|
|
last_name?: string | null;
|
|
is_active: boolean;
|
|
is_superuser: boolean;
|
|
is_verified: boolean;
|
|
// Optional JSON config object for user-level integrations and settings
|
|
// Example: { csas: "{\"expires_at\": 1761824615, ...}" } or { csas: { expires_at: 1761824615, ... } }
|
|
config?: Record<string, any> | null;
|
|
};
|
|
|
|
export async function getMe(): Promise<User> {
|
|
const res = await fetch(`${getBaseUrl()}/users/me`, {
|
|
headers: getHeaders(),
|
|
});
|
|
if (!res.ok) throw new Error('Failed to load user');
|
|
return res.json();
|
|
}
|
|
|
|
export type UpdateMeInput = Partial<Pick<User, 'first_name' | 'last_name'>> & { password?: string };
|
|
export async function updateMe(input: UpdateMeInput): Promise<User> {
|
|
const res = await fetch(`${getBaseUrl()}/users/me`, {
|
|
method: 'PATCH',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify(input),
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(text || 'Failed to update user');
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export async function deleteMe(): Promise<void> {
|
|
const res = await fetch(`${getBaseUrl()}/users/me`, {
|
|
method: 'DELETE',
|
|
headers: getHeaders(),
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(text || 'Failed to delete account');
|
|
}
|
|
}
|
|
|
|
export function logout() {
|
|
localStorage.removeItem('token');
|
|
}
|
|
|
|
// Categories
|
|
export type CreateCategoryInput = { name: string; description?: string };
|
|
export async function createCategory(input: CreateCategoryInput): Promise<Category> {
|
|
const res = await fetch(`${getBaseUrl()}/categories/create`, {
|
|
method: 'POST',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify(input),
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(text || 'Failed to create category');
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export type UpdateCategoryInput = { name?: string; description?: string };
|
|
export async function updateCategory(category_id: number, input: UpdateCategoryInput): Promise<Category> {
|
|
const res = await fetch(`${getBaseUrl()}/categories/${category_id}`, {
|
|
method: 'PATCH',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify(input),
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(text || 'Failed to update category');
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
// Transactions update
|
|
export type UpdateTransactionInput = {
|
|
amount?: number;
|
|
description?: string;
|
|
date?: string;
|
|
category_ids?: number[];
|
|
};
|
|
export async function updateTransaction(id: number, input: UpdateTransactionInput): Promise<Transaction> {
|
|
const res = await fetch(`${getBaseUrl()}/transactions/${id}/edit`, {
|
|
method: 'PATCH',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify(input),
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(text || 'Failed to update transaction');
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
// Balance series
|
|
export type BalancePoint = { date: string; balance: number };
|
|
export async function getBalanceSeries(start_date?: string, end_date?: string): Promise<BalancePoint[]> {
|
|
const params = new URLSearchParams();
|
|
if (start_date) params.set('start_date', start_date);
|
|
if (end_date) params.set('end_date', end_date);
|
|
const qs = params.toString();
|
|
const url = `${getBaseUrl()}/transactions/balance_series${qs ? `?${qs}` : ''}`;
|
|
const res = await fetch(url, { headers: getHeaders() });
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(text || 'Failed to load balance series');
|
|
}
|
|
return res.json();
|
|
}
|