From ee2f08cbfc08e137d0f1fb57dc1fbe9e9d60c4d2 Mon Sep 17 00:00:00 2001 From: jableader Date: Sat, 18 Oct 2025 12:30:30 +1100 Subject: [PATCH] Initial --- refactor-strategy.md | 62 +++++++++++++++++++++++++++ src/App.vue | 6 --- src/api/http.js | 30 +++++++++++++ src/api/mappers/mealMapper.js | 16 +++++++ src/api/meals.js | 18 ++++++++ src/components/meals/MealPlanPage.vue | 16 +++---- src/main.js | 50 +++------------------ src/router/index.js | 44 +++++++++++++++++++ 8 files changed, 183 insertions(+), 59 deletions(-) create mode 100644 refactor-strategy.md create mode 100644 src/api/http.js create mode 100644 src/api/mappers/mealMapper.js create mode 100644 src/api/meals.js create mode 100644 src/router/index.js diff --git a/refactor-strategy.md b/refactor-strategy.md new file mode 100644 index 0000000..8731009 --- /dev/null +++ b/refactor-strategy.md @@ -0,0 +1,62 @@ +# Refactor Strategy + +This document outlines a pragmatic, step-by-step refactor plan to improve structure, readability, and maintainability. Each step includes a clear outcome and a checkbox to track progress. + +Last updated: 2025-10-18 + +## Goals +- Separate concerns (routing, HTTP/API, mapping/normalization, UI logic) +- Improve readability and testability +- Establish light-weight standards (naming, lint/format) without blocking development +- Keep changes incremental and safe + +## Phases and Steps + +### Phase 1 — Routing and Auth (Foundational) +- [x] Extract router into `src/router/index.js` with named routes +- [x] Add route meta `requiresAuth` and a global auth guard +- [x] Remove auth-redirect from `App.vue` (handled by guard instead) +- [ ] Convert top-level nav to use route names consistently (optional) + +Outcome: Routing logic is centralized and testable; pages redirect consistently based on auth. + +### Phase 2 — API Layer Split (Incremental) +- [x] Add `src/api/http.js` wrapper for JSON fetch with error handling and env-based base URL +- [x] Add `src/api/mappers/mealMapper.js` to normalize Meal data (dates) +- [x] Add `src/api/meals.js` and migrate MealPlan API calls (get upcoming, mark consumed, delete) +- [ ] Create `src/api/recipes.js` and migrate recipe endpoints +- [ ] Create `src/api/shopping.js` and migrate shopping endpoints +- [ ] Create `src/api/auth.js` and migrate auth endpoints + +Outcome: Feature modules call cohesive services; logic for mapping/normalization is isolated and testable. + +### Phase 3 — Composables (UI-Facing Logic) +- [ ] Add `src/composables/useAuth.js` (user ref, ensureAuth) +- [ ] Add `src/composables/useMeals.js` (fetch and mutate meals) +- [ ] Refactor pages to use composables and ` diff --git a/src/api/http.js b/src/api/http.js new file mode 100644 index 0000000..d5fbc7f --- /dev/null +++ b/src/api/http.js @@ -0,0 +1,30 @@ +const BASE = process.env.VUE_APP_API_BASE || '/api' + +async function request(path, options = {}) { + const url = path.startsWith('http') ? path : `${BASE}${path}` + const resp = await fetch(url, { + credentials: 'include', + ...options, + headers: { + 'Accept': 'application/json', + ...(options.body ? { 'Content-Type': 'application/json' } : {}), + ...(options.headers || {}), + }, + }) + if (!resp.ok) { + let message = `${resp.status} ${resp.statusText}` + try { const err = await resp.json(); message = err.message || message } catch (_) { /* ignore */ } + const error = new Error(message) + error.status = resp.status + throw error + } + if (resp.status === 204) return null + return resp.json() +} + +export const http = { + get: (p) => request(p), + post: (p, body) => request(p, { method: 'POST', body: JSON.stringify(body) }), + put: (p, body) => request(p, { method: 'PUT', body: JSON.stringify(body) }), + del: (p) => request(p, { method: 'DELETE' }), +} diff --git a/src/api/mappers/mealMapper.js b/src/api/mappers/mealMapper.js new file mode 100644 index 0000000..f3973ae --- /dev/null +++ b/src/api/mappers/mealMapper.js @@ -0,0 +1,16 @@ +function toDate(value) { + return value ? new Date(value) : value +} + +export function mapMeal(meal) { + if (!meal) return meal + meal.suggested_date = toDate(meal.suggested_date) + meal.purchase_date = toDate(meal.purchase_date) + meal.consumed_date = toDate(meal.consumed_date) + return meal +} + +export function mapMeals(list) { + if (!Array.isArray(list)) return [] + return list.map(mapMeal) +} diff --git a/src/api/meals.js b/src/api/meals.js new file mode 100644 index 0000000..ed02936 --- /dev/null +++ b/src/api/meals.js @@ -0,0 +1,18 @@ +import { http } from './http' +import { mapMeal, mapMeals } from './mappers/mealMapper' + +export async function getUpcomingMeals(from, to) { + const params = `?from=${encodeURIComponent(from.toISOString())}&to=${encodeURIComponent(to.toISOString())}` + const meals = await http.get(`/meals/upcoming${params}`) + const normalized = mapMeals(meals) + return normalized.sort((a, b) => a.suggested_date - b.suggested_date) +} + +export async function markMealConsumed(mealId) { + const meal = await http.post(`/meals/${encodeURIComponent(mealId)}/consumed`) + return mapMeal(meal) +} + +export async function deleteMeal(mealId) { + return http.del(`/meals/${encodeURIComponent(mealId)}`) +} diff --git a/src/components/meals/MealPlanPage.vue b/src/components/meals/MealPlanPage.vue index 9940678..1a54975 100644 --- a/src/components/meals/MealPlanPage.vue +++ b/src/components/meals/MealPlanPage.vue @@ -81,7 +81,7 @@ ul.actions {