add vikunja integration

This commit is contained in:
Matthias Hochmeister
2026-03-05 18:07:18 +01:00
parent fb5acd3d52
commit e9463c1c66
13 changed files with 683 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
import axios from 'axios';
import environment from '../config/environment';
import logger from '../utils/logger';
export interface VikunjaTask {
id: number;
title: string;
done: boolean;
due_date: string | null;
priority: number;
project_id: number;
}
export interface VikunjaProject {
id: number;
title: string;
}
function buildHeaders(): Record<string, string> {
return {
'Authorization': `Bearer ${environment.vikunja.apiToken}`,
'Content-Type': 'application/json',
};
}
async function getMyTasks(): Promise<VikunjaTask[]> {
const { vikunja } = environment;
if (!vikunja.url) {
throw new Error('VIKUNJA_URL is not configured');
}
try {
const response = await axios.get<VikunjaTask[]>(
`${vikunja.url}/api/v1/tasks/all`,
{ headers: buildHeaders() },
);
return (response.data ?? []).filter((t) => !t.done);
} catch (error) {
if (axios.isAxiosError(error)) {
logger.error('Vikunja getMyTasks failed', {
status: error.response?.status,
statusText: error.response?.statusText,
});
}
logger.error('VikunjaService.getMyTasks failed', { error });
throw new Error('Failed to fetch Vikunja tasks');
}
}
async function getOverdueTasks(): Promise<VikunjaTask[]> {
const tasks = await getMyTasks();
const now = new Date();
return tasks.filter((t) => {
if (!t.due_date) return false;
return new Date(t.due_date) < now;
});
}
async function getProjects(): Promise<VikunjaProject[]> {
const { vikunja } = environment;
if (!vikunja.url) {
throw new Error('VIKUNJA_URL is not configured');
}
try {
const response = await axios.get<VikunjaProject[]>(
`${vikunja.url}/api/v1/projects`,
{ headers: buildHeaders() },
);
return response.data ?? [];
} catch (error) {
if (axios.isAxiosError(error)) {
logger.error('Vikunja getProjects failed', {
status: error.response?.status,
statusText: error.response?.statusText,
});
}
logger.error('VikunjaService.getProjects failed', { error });
throw new Error('Failed to fetch Vikunja projects');
}
}
async function createTask(projectId: number, title: string, dueDate?: string): Promise<VikunjaTask> {
const { vikunja } = environment;
if (!vikunja.url) {
throw new Error('VIKUNJA_URL is not configured');
}
try {
const body: Record<string, unknown> = { title };
if (dueDate) {
body.due_date = dueDate;
}
const response = await axios.put<VikunjaTask>(
`${vikunja.url}/api/v1/projects/${projectId}/tasks`,
body,
{ headers: buildHeaders() },
);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
logger.error('Vikunja createTask failed', {
status: error.response?.status,
statusText: error.response?.statusText,
});
}
logger.error('VikunjaService.createTask failed', { error });
throw new Error('Failed to create Vikunja task');
}
}
export default { getMyTasks, getOverdueTasks, getProjects, createTask };