diff --git a/be/app/core/api_config.py b/be/app/core/api_config.py
new file mode 100644
index 0000000..79af045
--- /dev/null
+++ b/be/app/core/api_config.py
@@ -0,0 +1,102 @@
+from typing import Dict, Any
+from app.config import settings
+
+# API Version
+API_VERSION = "v1"
+
+# API Prefix
+API_PREFIX = f"/api/{API_VERSION}"
+
+# API Endpoints
+class APIEndpoints:
+ # Auth
+ AUTH = {
+ "LOGIN": "/auth/login",
+ "SIGNUP": "/auth/signup",
+ "REFRESH_TOKEN": "/auth/refresh-token",
+ }
+
+ # Users
+ USERS = {
+ "PROFILE": "/users/profile",
+ "UPDATE_PROFILE": "/users/profile",
+ }
+
+ # Lists
+ LISTS = {
+ "BASE": "/lists",
+ "BY_ID": "/lists/{id}",
+ "ITEMS": "/lists/{list_id}/items",
+ "ITEM": "/lists/{list_id}/items/{item_id}",
+ }
+
+ # Groups
+ GROUPS = {
+ "BASE": "/groups",
+ "BY_ID": "/groups/{id}",
+ "LISTS": "/groups/{group_id}/lists",
+ "MEMBERS": "/groups/{group_id}/members",
+ }
+
+ # Invites
+ INVITES = {
+ "BASE": "/invites",
+ "BY_ID": "/invites/{id}",
+ "ACCEPT": "/invites/{id}/accept",
+ "DECLINE": "/invites/{id}/decline",
+ }
+
+ # OCR
+ OCR = {
+ "PROCESS": "/ocr/process",
+ }
+
+ # Financials
+ FINANCIALS = {
+ "EXPENSES": "/financials/expenses",
+ "EXPENSE": "/financials/expenses/{id}",
+ "SETTLEMENTS": "/financials/settlements",
+ "SETTLEMENT": "/financials/settlements/{id}",
+ }
+
+ # Health
+ HEALTH = {
+ "CHECK": "/health",
+ }
+
+# API Metadata
+API_METADATA = {
+ "title": settings.API_TITLE,
+ "description": settings.API_DESCRIPTION,
+ "version": settings.API_VERSION,
+ "openapi_url": settings.API_OPENAPI_URL,
+ "docs_url": settings.API_DOCS_URL,
+ "redoc_url": settings.API_REDOC_URL,
+}
+
+# API Tags
+API_TAGS = [
+ {"name": "Authentication", "description": "Authentication and authorization endpoints"},
+ {"name": "Users", "description": "User management endpoints"},
+ {"name": "Lists", "description": "Shopping list management endpoints"},
+ {"name": "Groups", "description": "Group management endpoints"},
+ {"name": "Invites", "description": "Group invitation management endpoints"},
+ {"name": "OCR", "description": "Optical Character Recognition endpoints"},
+ {"name": "Financials", "description": "Financial management endpoints"},
+ {"name": "Health", "description": "Health check endpoints"},
+]
+
+# Helper function to get full API URL
+def get_api_url(endpoint: str, **kwargs) -> str:
+ """
+ Get the full API URL for an endpoint.
+
+ Args:
+ endpoint: The endpoint path
+ **kwargs: Path parameters to format the endpoint
+
+ Returns:
+ str: The full API URL
+ """
+ formatted_endpoint = endpoint.format(**kwargs)
+ return f"{API_PREFIX}{formatted_endpoint}"
\ No newline at end of file
diff --git a/be/app/main.py b/be/app/main.py
index 0230e98..fca6ecb 100644
--- a/be/app/main.py
+++ b/be/app/main.py
@@ -4,8 +4,9 @@ import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
-from app.api.api_router import api_router # Import the main combined router
+from app.api.api_router import api_router
from app.config import settings
+from app.core.api_config import API_METADATA, API_TAGS
# Import database and models if needed for startup/shutdown events later
# from . import database, models
@@ -18,12 +19,8 @@ logger = logging.getLogger(__name__)
# --- FastAPI App Instance ---
app = FastAPI(
- title=settings.API_TITLE,
- description=settings.API_DESCRIPTION,
- version=settings.API_VERSION,
- openapi_url=settings.API_OPENAPI_URL,
- docs_url=settings.API_DOCS_URL,
- redoc_url=settings.API_REDOC_URL
+ **API_METADATA,
+ openapi_tags=API_TAGS
)
# --- CORS Middleware ---
diff --git a/fe/src/boot/axios.ts b/fe/src/boot/axios.ts
index ed9fb0f..377a391 100644
--- a/fe/src/boot/axios.ts
+++ b/fe/src/boot/axios.ts
@@ -1,70 +1,72 @@
import { boot } from 'quasar/wrappers';
-import axios, { type AxiosInstance } from 'axios';
-import { useAuthStore } from 'stores/auth';
-
-declare module '@vue/runtime-core' {
- interface ComponentCustomProperties {
- $axios: AxiosInstance;
- }
-}
+import axios, { AxiosInstance } from 'axios';
+import { API_BASE_URL, API_VERSION, API_ENDPOINTS } from 'src/config/api-config';
+// Create axios instance
const api = axios.create({
- baseURL: `${import.meta.env.VITE_API_URL || 'http://localhost:8000'}/api/v1`,
+ baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
-// Request interceptor for adding auth token
+// Request interceptor
api.interceptors.request.use(
(config) => {
- const authStore = useAuthStore();
- if (authStore.accessToken) {
- config.headers.Authorization = `Bearer ${authStore.accessToken}`;
+ const token = localStorage.getItem('token');
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
- return Promise.reject(new Error(error.message));
- },
+ return Promise.reject(error);
+ }
);
-// Response interceptor for handling errors
+// Response interceptor
api.interceptors.response.use(
(response) => response,
async (error) => {
- const authStore = useAuthStore();
+ const originalRequest = error.config;
+
+ // If error is 401 and we haven't tried to refresh token yet
+ if (error.response?.status === 401 && !originalRequest._retry) {
+ originalRequest._retry = true;
- // If the error is 401 and we have a refresh token, try to refresh the access token
- if (error.response?.status === 401 && authStore.refreshToken) {
try {
- await authStore.refreshAccessToken();
- // Retry the original request
- const config = error.config;
- config.headers.Authorization = `Bearer ${authStore.accessToken}`;
- return api(config);
- } catch (error) {
- // If refresh fails, clear tokens and redirect to login
- authStore.logout();
+ const refreshToken = localStorage.getItem('refreshToken');
+ if (!refreshToken) {
+ throw new Error('No refresh token available');
+ }
+
+ // Call refresh token endpoint
+ const response = await api.post('/api/v1/auth/refresh-token', {
+ refresh_token: refreshToken,
+ });
+
+ const { access_token } = response.data;
+ localStorage.setItem('token', access_token);
+
+ // Retry the original request with new token
+ originalRequest.headers.Authorization = `Bearer ${access_token}`;
+ return api(originalRequest);
+ } catch (refreshError) {
+ // If refresh token fails, clear storage and redirect to login
+ localStorage.removeItem('token');
+ localStorage.removeItem('refreshToken');
window.location.href = '/login';
- return Promise.reject(
- new Error(error instanceof Error ? error.message : 'Failed to refresh token'),
- );
+ return Promise.reject(refreshError);
}
}
- // If it's a 401 without refresh token or refresh failed, clear tokens and redirect
- if (error.response?.status === 401) {
- authStore.logout();
- window.location.href = '/login';
- }
-
- return Promise.reject(new Error(error.response?.data?.detail || error.message));
- },
+ return Promise.reject(error);
+ }
);
export default boot(({ app }) => {
- app.config.globalProperties.$axios = api;
+ app.config.globalProperties.$axios = axios;
+ app.config.globalProperties.$api = api;
});
export { api };
diff --git a/fe/src/components/CreateListModal.vue b/fe/src/components/CreateListModal.vue
index 03cf160..06914b4 100644
--- a/fe/src/components/CreateListModal.vue
+++ b/fe/src/components/CreateListModal.vue
@@ -39,7 +39,7 @@
diff --git a/fe/src/pages/GroupDetailPage.vue b/fe/src/pages/GroupDetailPage.vue
index 805a024..4e9e509 100644
--- a/fe/src/pages/GroupDetailPage.vue
+++ b/fe/src/pages/GroupDetailPage.vue
@@ -34,7 +34,7 @@