- Added `lib/api.ts` to centralize API communication for authentication, projects, persons, tags, and groups. - Introduced `middleware.ts` to handle route protection based on authentication and roles. - Created `auth-context.tsx` to manage authentication state with `AuthProvider` and `useAuth` hook. - Updated `package.json` to include `swr` for data fetching. - Enhanced project documentation (`RESPONSIVE_DESIGN.md` and `README.md`) with responsive design and architecture details.
302 lines
6.0 KiB
TypeScript
302 lines
6.0 KiB
TypeScript
/**
|
|
* API Service
|
|
*
|
|
* This service centralizes all API communication with the backend.
|
|
* It provides methods for authentication, projects, persons, and tags.
|
|
*/
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
|
|
|
|
/**
|
|
* Base fetch function with error handling and authentication
|
|
*/
|
|
async function fetchAPI(endpoint: string, options: RequestInit = {}) {
|
|
// Set default headers
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
...(options.headers as Record<string, string> || {}),
|
|
};
|
|
|
|
// Get token from localStorage if available (client-side only)
|
|
if (typeof window !== 'undefined') {
|
|
const token = localStorage.getItem('auth_token');
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
}
|
|
|
|
// Prepare fetch options
|
|
const fetchOptions: RequestInit = {
|
|
...options,
|
|
headers,
|
|
credentials: 'include', // Include cookies for session management
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(`${API_URL}${endpoint}`, fetchOptions);
|
|
|
|
// Handle HTTP errors
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.message || `API error: ${response.status}`);
|
|
}
|
|
|
|
// Parse JSON response if content exists
|
|
const contentType = response.headers.get('content-type');
|
|
if (contentType && contentType.includes('application/json')) {
|
|
return await response.json();
|
|
}
|
|
|
|
return response;
|
|
} catch (error) {
|
|
console.error('API request failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Authentication API
|
|
*/
|
|
export const authAPI = {
|
|
/**
|
|
* Get GitHub OAuth URL
|
|
*/
|
|
getGitHubOAuthUrl: async () => {
|
|
return fetchAPI('/auth/github', { method: 'GET' });
|
|
},
|
|
|
|
/**
|
|
* Exchange code for access token
|
|
*/
|
|
githubCallback: async (code: string) => {
|
|
return fetchAPI('/auth/github/callback', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ code }),
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Logout user
|
|
*/
|
|
logout: async () => {
|
|
return fetchAPI('/auth/logout', { method: 'POST' });
|
|
},
|
|
|
|
/**
|
|
* Get current user
|
|
*/
|
|
getCurrentUser: async () => {
|
|
return fetchAPI('/auth/me', { method: 'GET' });
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Projects API
|
|
*/
|
|
export const projectsAPI = {
|
|
/**
|
|
* Get all projects
|
|
*/
|
|
getProjects: async () => {
|
|
return fetchAPI('/projects', { method: 'GET' });
|
|
},
|
|
|
|
/**
|
|
* Get project by ID
|
|
*/
|
|
getProject: async (id: string) => {
|
|
return fetchAPI(`/projects/${id}`, { method: 'GET' });
|
|
},
|
|
|
|
/**
|
|
* Create new project
|
|
*/
|
|
createProject: async (data: any) => {
|
|
return fetchAPI('/projects', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Update project
|
|
*/
|
|
updateProject: async (id: string, data: any) => {
|
|
return fetchAPI(`/projects/${id}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(data),
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Delete project
|
|
*/
|
|
deleteProject: async (id: string) => {
|
|
return fetchAPI(`/projects/${id}`, { method: 'DELETE' });
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Persons API
|
|
*/
|
|
export const personsAPI = {
|
|
/**
|
|
* Get all persons for a project
|
|
*/
|
|
getPersons: async (projectId: string) => {
|
|
return fetchAPI(`/projects/${projectId}/persons`, { method: 'GET' });
|
|
},
|
|
|
|
/**
|
|
* Get person by ID
|
|
*/
|
|
getPerson: async (id: string) => {
|
|
return fetchAPI(`/persons/${id}`, { method: 'GET' });
|
|
},
|
|
|
|
/**
|
|
* Create new person
|
|
*/
|
|
createPerson: async (projectId: string, data: any) => {
|
|
return fetchAPI(`/projects/${projectId}/persons`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Update person
|
|
*/
|
|
updatePerson: async (id: string, data: any) => {
|
|
return fetchAPI(`/persons/${id}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(data),
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Delete person
|
|
*/
|
|
deletePerson: async (id: string) => {
|
|
return fetchAPI(`/persons/${id}`, { method: 'DELETE' });
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Tags API
|
|
*/
|
|
export const tagsAPI = {
|
|
/**
|
|
* Get all tags
|
|
*/
|
|
getTags: async () => {
|
|
return fetchAPI('/tags', { method: 'GET' });
|
|
},
|
|
|
|
/**
|
|
* Get tag by ID
|
|
*/
|
|
getTag: async (id: string) => {
|
|
return fetchAPI(`/tags/${id}`, { method: 'GET' });
|
|
},
|
|
|
|
/**
|
|
* Create new tag
|
|
*/
|
|
createTag: async (data: any) => {
|
|
return fetchAPI('/tags', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Update tag
|
|
*/
|
|
updateTag: async (id: string, data: any) => {
|
|
return fetchAPI(`/tags/${id}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(data),
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Delete tag
|
|
*/
|
|
deleteTag: async (id: string) => {
|
|
return fetchAPI(`/tags/${id}`, { method: 'DELETE' });
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Groups API
|
|
*/
|
|
export const groupsAPI = {
|
|
/**
|
|
* Get all groups for a project
|
|
*/
|
|
getGroups: async (projectId: string) => {
|
|
return fetchAPI(`/projects/${projectId}/groups`, { method: 'GET' });
|
|
},
|
|
|
|
/**
|
|
* Get group by ID
|
|
*/
|
|
getGroup: async (id: string) => {
|
|
return fetchAPI(`/groups/${id}`, { method: 'GET' });
|
|
},
|
|
|
|
/**
|
|
* Create new group
|
|
*/
|
|
createGroup: async (projectId: string, data: any) => {
|
|
return fetchAPI(`/projects/${projectId}/groups`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Update group
|
|
*/
|
|
updateGroup: async (id: string, data: any) => {
|
|
return fetchAPI(`/groups/${id}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(data),
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Delete group
|
|
*/
|
|
deleteGroup: async (id: string) => {
|
|
return fetchAPI(`/groups/${id}`, { method: 'DELETE' });
|
|
},
|
|
|
|
/**
|
|
* Add person to group
|
|
*/
|
|
addPersonToGroup: async (groupId: string, personId: string) => {
|
|
return fetchAPI(`/groups/${groupId}/persons/${personId}`, {
|
|
method: 'POST',
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Remove person from group
|
|
*/
|
|
removePersonFromGroup: async (groupId: string, personId: string) => {
|
|
return fetchAPI(`/groups/${groupId}/persons/${personId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
},
|
|
};
|
|
|
|
export default {
|
|
auth: authAPI,
|
|
projects: projectsAPI,
|
|
persons: personsAPI,
|
|
tags: tagsAPI,
|
|
groups: groupsAPI,
|
|
};
|