You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
590 lines
18 KiB
590 lines
18 KiB
/**
|
|
* APIサービス
|
|
* バックエンドAPIとの通信を担当するモジュール
|
|
* 認証、タスク管理などの機能を提供
|
|
*/
|
|
import { LoginCredentials, RegisterCredentials, AuthResponse, /* Task, */ ToBuy, Stuff, Stock, RecipeDetail, StuffAndCategoryAndAmount, RecipeWithId, StockHistory, StockUpdateRequest } from '../types/types';
|
|
import { AUTH_ERRORS, TOBUY_ERRORS, STOCK_ERRORS, RECIPE_ERRORS } from '../constants/errorMessages';
|
|
|
|
// APIのベースURL - 環境変数から取得するか、デフォルト値を使用
|
|
const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://localhost:8080';
|
|
|
|
/**
|
|
* APIリクエスト用のヘッダーを生成する関数
|
|
* @param includeAuth 認証トークンをヘッダーに含めるかどうか
|
|
* @returns リクエストヘッダーオブジェクト
|
|
*/
|
|
const getHeaders = (includeAuth: boolean = true) => {
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
// 認証トークンが必要な場合はローカルストレージから取得してヘッダーに追加
|
|
if (includeAuth) {
|
|
const token = localStorage.getItem('token');
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
}
|
|
|
|
return headers;
|
|
};
|
|
|
|
/**
|
|
* 認証関連のAPI機能を提供するオブジェクト
|
|
* ログインと新規ユーザー登録の機能を含む
|
|
*/
|
|
export const authApi = {
|
|
/**
|
|
* ユーザーログイン処理
|
|
* @param credentials ログイン情報(ユーザー名とパスワード)
|
|
* @returns 認証レスポンス(トークンとユーザー情報)
|
|
*/
|
|
login: async (credentials: LoginCredentials): Promise<AuthResponse> => {
|
|
const response = await fetch(`${API_BASE_URL}/api/auth/login`, {
|
|
method: 'POST',
|
|
headers: getHeaders(false), // 認証前なのでトークンは不要
|
|
body: JSON.stringify(credentials),
|
|
});
|
|
|
|
// エラーレスポンスの処理
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => null);
|
|
throw new Error(
|
|
errorData?.message || AUTH_ERRORS.LOGIN_FAILED
|
|
);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* 新規ユーザー登録処理
|
|
* @param credentials 登録情報(ユーザー名、パスワード)
|
|
* @returns 認証レスポンス(トークンとユーザー情報)
|
|
*/
|
|
register: async (credentials: RegisterCredentials): Promise<AuthResponse> => {
|
|
const response = await fetch(`${API_BASE_URL}/api/auth/register`, {
|
|
method: 'POST',
|
|
headers: getHeaders(false), // 認証前なのでトークンは不要
|
|
body: JSON.stringify(credentials),
|
|
});
|
|
|
|
// エラーレスポンスの処理
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => null);
|
|
throw new Error(
|
|
errorData?.message || AUTH_ERRORS.REGISTER_FAILED
|
|
);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
};
|
|
|
|
|
|
/**
|
|
* 買うものリスト管理関連のAPI機能を提供するオブジェクト
|
|
* 買うものリストの取得、作成、更新、削除などの機能を含む
|
|
*/
|
|
export const toBuyApi = {
|
|
/**
|
|
* 全買うものリストを取得
|
|
* @returns 買うものリスト一覧
|
|
*/
|
|
getToBuys: async (): Promise<ToBuy[]> => {
|
|
const response = await fetch(`${API_BASE_URL}/api/tobuy/get`, {
|
|
headers: getHeaders(), // 認証トークンを含むヘッダー
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(TOBUY_ERRORS.FETCH_FAILED);
|
|
}
|
|
|
|
return await response.json();
|
|
},
|
|
|
|
/**
|
|
* 買うものリストへの材料追加
|
|
* @param tobuy 作成する材料情報
|
|
* @returns 作成された材料情報
|
|
*/
|
|
addToBuy: async (tobuy: Omit<ToBuy, 'stuffId' | 'tobuyId'> & { stuffId: number | null, category: string }): Promise<any> => {
|
|
const response = await fetch(`${API_BASE_URL}/api/tobuy/add`, {
|
|
method: 'POST',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify(tobuy),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(TOBUY_ERRORS.CREATE_FAILED);
|
|
}
|
|
|
|
return response.json();
|
|
// return {result: true}
|
|
|
|
// return {
|
|
// "result": true,
|
|
// "tobuyId": 1,
|
|
// "stuffId": 6,
|
|
// "message": "追加に成功しました",
|
|
// }
|
|
|
|
},
|
|
|
|
addByRecipe: async (recipeId: number, servings: number, difference: boolean): Promise<any> => {
|
|
const response = await fetch(`${API_BASE_URL}/api/tobuy/addByRecipe`, {
|
|
method: 'POST',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify({ recipeId, servings, difference }),
|
|
})
|
|
if (!response.ok) {
|
|
throw new Error(TOBUY_ERRORS.CREATE_FAILED);
|
|
}
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* 買うものリストを削除
|
|
* @param id 削除対象の買うものリストID
|
|
*/
|
|
deleteToBuy: async (tobuyId: number): Promise<{ result: boolean }> => {
|
|
const response = await fetch(`${API_BASE_URL}/api/tobuy/delete`, {
|
|
method: 'DELETE',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify({ tobuyId }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(TOBUY_ERRORS.DELETE_FAILED);
|
|
}
|
|
|
|
return response.json()
|
|
|
|
// return {
|
|
// "result": true
|
|
// }
|
|
},
|
|
|
|
/**
|
|
* 買うものリストの変更
|
|
* @param tobuy 変更する材料情報
|
|
* @returns 変更された材料情報
|
|
*/
|
|
updateToBuy: async (tobuy: ToBuy): Promise<any> => {
|
|
const response = await fetch(`${API_BASE_URL}/api/tobuy/update`, {
|
|
method: 'PUT',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify(tobuy),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(TOBUY_ERRORS.CREATE_FAILED);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* 買うものリストの在庫登録(購入処理)
|
|
*/
|
|
buy: async (req: { tobuyId: number, amount: number, price: number, shop: string, expDate: string, buyDate: string, lastUpdate: string }): Promise<{ result: boolean }> => {
|
|
|
|
console.log('/api/tobuy/buy request: ', req)
|
|
|
|
req.buyDate = makeDateObject(req.buyDate)?.toISOString()?.substring(0, 10) || ''
|
|
req.expDate = makeDateObject(req.expDate)?.toISOString()?.substring(0, 10) || ''
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/tobuy/buy`, {
|
|
method: 'POST',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify(req),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(TOBUY_ERRORS.BUY_FAILED);
|
|
}
|
|
|
|
return response.json()
|
|
},
|
|
|
|
}
|
|
|
|
export const stuffApi = {
|
|
getStuffs: async (category: string): Promise<Stuff[]> => {
|
|
const data = await fetch(`${API_BASE_URL}/api/stuff/get?category=${encodeURIComponent(category)}`, {
|
|
headers: getHeaders(), // 認証トークンを含むヘッダー
|
|
});
|
|
|
|
if (!data.ok) {
|
|
throw new Error(`Failed to fetch stuffs for category ${category}`);
|
|
}
|
|
return data.json();
|
|
// const data = [
|
|
// { stuffId: 1, stuffName: "牛乳", category: "乳製品" },
|
|
// { stuffId: 2, stuffName: "ヨーグルト", category: "乳製品" },
|
|
// { stuffId: 3, stuffName: "チーズ", category: "乳製品" },
|
|
// { stuffId: 4, stuffName: "バター", category: "乳製品" },
|
|
// { stuffId: 5, stuffName: "生クリーム", category: "乳製品" },
|
|
|
|
// { stuffId: 6, stuffName: "鮭", category: "魚・肉" },
|
|
// { stuffId: 7, stuffName: "鶏むね肉", category: "魚・肉" },
|
|
// { stuffId: 8, stuffName: "豚バラ肉", category: "魚・肉" },
|
|
// { stuffId: 9, stuffName: "牛ひき肉", category: "魚・肉" },
|
|
// { stuffId: 10, stuffName: "まぐろ", category: "魚・肉" },
|
|
|
|
// { stuffId: 11, stuffName: "にんじん", category: "野菜" },
|
|
// { stuffId: 12, stuffName: "キャベツ", category: "野菜" },
|
|
// { stuffId: 13, stuffName: "ほうれん草", category: "野菜" },
|
|
// { stuffId: 14, stuffName: "玉ねぎ", category: "野菜" },
|
|
// { stuffId: 15, stuffName: "ピーマン", category: "野菜" },
|
|
|
|
// { stuffId: 16, stuffName: "醤油", category: "調味料" },
|
|
// { stuffId: 17, stuffName: "味噌", category: "調味料" },
|
|
// { stuffId: 18, stuffName: "塩", category: "調味料" },
|
|
// { stuffId: 19, stuffName: "砂糖", category: "調味料" },
|
|
// { stuffId: 20, stuffName: "酢", category: "調味料" },
|
|
|
|
// { stuffId: 21, stuffName: "米", category: "その他" },
|
|
// { stuffId: 22, stuffName: "パスタ", category: "その他" },
|
|
// { stuffId: 23, stuffName: "小麦粉", category: "その他" },
|
|
// { stuffId: 24, stuffName: "卵", category: "その他" },
|
|
// { stuffId: 25, stuffName: "豆腐", category: "その他" }
|
|
// ]
|
|
|
|
// const filtered = data.filter(stuff => stuff.category == category)
|
|
|
|
// return filtered
|
|
}
|
|
}
|
|
|
|
|
|
export const stockApi = {
|
|
/**
|
|
* 全在庫リストを取得
|
|
* @returns 買在庫リスト一覧
|
|
*/
|
|
getStocks: async (): Promise<Stock[]> => {
|
|
const response = await fetch(`${API_BASE_URL}/api/stocks/get`, {
|
|
headers: getHeaders(), // 認証トークンを含むヘッダー
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(STOCK_ERRORS.FETCH_FAILED);
|
|
}
|
|
|
|
return response.json();
|
|
|
|
},
|
|
|
|
/**
|
|
* 在庫リストへの新規食材追加
|
|
* @param stock 作成する材料情報
|
|
* @returns 作成された材料情報
|
|
*/
|
|
addStock: async (stock: Omit<Stock, 'stockId' | 'stuffId'> & { stuffId: number | null }): Promise<{ result: boolean; message: string }> => {
|
|
|
|
console.log("送信するデータ:", stock); // 送信前のデータ確認
|
|
|
|
stock.buyDate = makeDateObject(stock.buyDate)?.toISOString()?.substring(0, 10) || ''
|
|
stock.expDate = makeDateObject(stock.expDate)?.toISOString()?.substring(0, 10) || ''
|
|
|
|
console.log("変換後のデータ:", stock); // 日付変換後のデータ確認
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/stocks/add`, {
|
|
method: 'POST',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify(stock),
|
|
});
|
|
|
|
console.log("レスポンスステータス:", response.status);
|
|
console.log("レスポンスヘッダー:", response.headers);
|
|
// console.log("レスポンス内容:", await response.text());
|
|
|
|
|
|
if (!response.ok) {
|
|
throw new Error(STOCK_ERRORS.CREATE_FAILED);
|
|
}
|
|
|
|
return response.json();
|
|
// return {result: true}
|
|
|
|
},
|
|
|
|
/**
|
|
* 在庫リストの編集
|
|
*/
|
|
updateStock: async (req: StockUpdateRequest): Promise<{ result: boolean; message: string }> => {
|
|
// console.log('req: ', req)
|
|
|
|
req.buyDate = makeDateObject(req.buyDate)?.toISOString()?.substring(0, 10) || ''
|
|
req.expDate = makeDateObject(req.expDate)?.toISOString()?.substring(0, 10) || ''
|
|
|
|
console.log('req: ', req)
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/stocks/update`, {
|
|
method: 'PUT',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify(req),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(STOCK_ERRORS.UPDATE_FAILED);
|
|
}
|
|
|
|
return response.json()
|
|
|
|
// return {
|
|
// "result": true
|
|
// }
|
|
},
|
|
|
|
/**
|
|
* 在庫リストの削除
|
|
* @param id 削除対象の食材のID
|
|
*/
|
|
deleteStock: async (stockId: number): Promise<{ result: boolean; message: string }> => {
|
|
const response = await fetch(`${API_BASE_URL}/api/stocks/delete`, {
|
|
method: 'DELETE',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify({ stockId }),
|
|
});
|
|
|
|
console.log("API レスポンスステータス:", response.status);
|
|
// console.log("API レスポンス本文:", await response.text());
|
|
|
|
if (!response.ok) {
|
|
throw new Error(STOCK_ERRORS.DELETE_FAILED);
|
|
}
|
|
|
|
const response_json = response.json()
|
|
console.log('Delete response:', response_json)
|
|
return response_json
|
|
|
|
// return {
|
|
// "result": true
|
|
// }
|
|
},
|
|
|
|
/**
|
|
* 指定した材料の履歴を取得
|
|
* @param recipeId 取得対象の材料ID
|
|
* @returns 材料の履歴の配列
|
|
*/
|
|
getHistories: async (stuffId: number): Promise<StockHistory[]> => {
|
|
const response = await fetch(`${API_BASE_URL}/api/stocks/getHistory?stuffId=${stuffId}`, {
|
|
method: 'GET',
|
|
headers: getHeaders(),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => null);
|
|
throw new Error(errorData?.message);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
};
|
|
|
|
/**
|
|
* レシピ管理関連のAPI機能を提供するオブジェクト
|
|
*/
|
|
export const recipeApi = {
|
|
/**
|
|
* 新規レシピ追加処理
|
|
* @param recipeData レシピデータ(名前、説明、材料リスト)
|
|
* @returns レシピ追加レスポンス(内联类型)
|
|
*/
|
|
addRecipe: async (recipeData: RecipeDetail): Promise<{
|
|
result: string;
|
|
recipeId: number;
|
|
message: string;
|
|
}> => {
|
|
console.log('recipeData:', recipeData)
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/recipes/add`, {
|
|
method: 'POST',
|
|
headers: getHeaders(), // 認証トークンを含むヘッダー
|
|
body: JSON.stringify(recipeData),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => null);
|
|
console.error('/api/recipes/add failed on backend:', errorData?.message)
|
|
throw new Error(RECIPE_ERRORS.CREATE_FAILED);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* レシピを更新
|
|
* @param recipeData 更新するレシピ情報
|
|
* @returns 更新結果(成功/失敗)
|
|
*/
|
|
updateRecipe: async (recipeData: { recipeId: number } & RecipeDetail): Promise<{ result: boolean; message: string }> => {
|
|
// console.log('recipeData:', recipeData)
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/recipes/update`, {
|
|
method: 'PUT',
|
|
headers: getHeaders(),
|
|
body: JSON.stringify(recipeData),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => null);
|
|
console.error('/api/recipes/update failed on backend:', errorData?.message)
|
|
throw new Error(RECIPE_ERRORS.UPDATE_FAILED);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* 全レシピを取得
|
|
* @returns レシピ一覧
|
|
*/
|
|
getAllRecipes: async (): Promise<RecipeWithId[]> => {
|
|
const response = await fetch(`${API_BASE_URL}/api/recipes/getAll`, {
|
|
method: 'GET',
|
|
headers: getHeaders(), // 認証トークンを含むヘッダー
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => null);
|
|
throw new Error(
|
|
errorData?.message
|
|
);
|
|
}
|
|
return response.json();
|
|
},
|
|
|
|
/**
|
|
* レシピ詳細を取得
|
|
* @param recipeId 取得対象のレシピID
|
|
* @returns レシピ詳細情報
|
|
*/
|
|
getById: async (recipeId: number): Promise<{
|
|
recipeId: number;
|
|
recipeName: string;
|
|
summary: string;
|
|
stuffAndAmountArray: StuffAndCategoryAndAmount[];
|
|
}> => {
|
|
const response = await fetch(`${API_BASE_URL}/api/recipes/getById?recipeId=${recipeId}`, {
|
|
method: 'GET',
|
|
headers: getHeaders(),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => null);
|
|
throw new Error(errorData?.message);
|
|
}
|
|
|
|
return response.json();
|
|
},
|
|
};
|
|
|
|
|
|
function makeDateObject(dateStr: String) {
|
|
// 例: '2025/06/15' または '2025-06-15' を '2025-06-15' に変換
|
|
const parts = dateStr.split(/[-\/]/); // ハイフンかスラッシュで分割
|
|
if (parts.length === 3) {
|
|
return new Date(parts[0] + '-' + parts[1] + '-' + parts[2]);
|
|
}
|
|
return null; // 無効な日付の場合
|
|
}
|
|
|
|
|
|
// /**
|
|
// * (サンプル,実際には不要)
|
|
// * タスク管理関連のAPI機能を提供するオブジェクト
|
|
// * タスクの取得、作成、更新、削除などの機能を含む
|
|
// */
|
|
// export const taskApi = {
|
|
// /**
|
|
// * 全タスクを取得
|
|
// * @returns タスク一覧
|
|
// */
|
|
// getTasks: async (): Promise<Task[]> => {
|
|
// const response = await fetch(`${API_BASE_URL}/api/tasks`, {
|
|
// headers: getHeaders(), // 認証トークンを含むヘッダー
|
|
// });
|
|
|
|
// if (!response.ok) {
|
|
// throw new Error(TASK_ERRORS.FETCH_FAILED);
|
|
// }
|
|
|
|
// return response.json();
|
|
// },
|
|
|
|
// /**
|
|
// * 指定IDのタスクを取得
|
|
// * @param id タスクID
|
|
// * @returns 単一のタスク情報
|
|
// */
|
|
// getTask: async (id: number): Promise<Task> => {
|
|
// const response = await fetch(`${API_BASE_URL}/api/tasks/${id}`, {
|
|
// headers: getHeaders(),
|
|
// });
|
|
|
|
// if (!response.ok) {
|
|
// throw new Error(TASK_ERRORS.FETCH_FAILED);
|
|
// }
|
|
|
|
// return response.json();
|
|
// },
|
|
|
|
// /**
|
|
// * 新規材料を作成
|
|
// * @param task 作成するタスク情報(価格,作成日時、更新日時は除外)
|
|
// * @returns 作成されたタスク情報
|
|
// */
|
|
// addStuff: async (task: Omit<Task, 'userId' | 'createdAt' | 'price' | 'buyDate' | 'expirationDate' | 'newAddition'>): Promise<Task> => {
|
|
// const response = await fetch(`${API_BASE_URL}/api/tubuy/add`, {
|
|
// method: 'POST',
|
|
// headers: getHeaders(),
|
|
// body: JSON.stringify(task),
|
|
// });
|
|
|
|
// if (!response.ok) {
|
|
// throw new Error(TASK_ERRORS.CREATE_FAILED);
|
|
// }
|
|
|
|
// return response.json();
|
|
// },
|
|
|
|
// /**
|
|
// * タスクを更新
|
|
// * @param id 更新対象のタスクID
|
|
// * @param task 更新するタスク情報(部分的な更新も可能)
|
|
// * @returns 更新後のタスク情報
|
|
// */
|
|
// updateTask: async (id: number, task: Partial<Task>): Promise<Task> => {
|
|
// const response = await fetch(`${API_BASE_URL}/api/tasks/${id}`, {
|
|
// method: 'PUT',
|
|
// headers: getHeaders(),
|
|
// body: JSON.stringify(task),
|
|
// });
|
|
|
|
// if (!response.ok) {
|
|
// throw new Error(TASK_ERRORS.UPDATE_FAILED);
|
|
// }
|
|
|
|
// return response.json();
|
|
// },
|
|
|
|
// /**
|
|
// * タスクを削除
|
|
// * @param id 削除対象のタスクID
|
|
// */
|
|
// deleteTask: async (id: number): Promise<void> => {
|
|
// const response = await fetch(`${API_BASE_URL}/api/tasks/${id}`, {
|
|
// method: 'DELETE',
|
|
// headers: getHeaders(),
|
|
// });
|
|
|
|
// if (!response.ok) {
|
|
// throw new Error(TASK_ERRORS.DELETE_FAILED);
|
|
// }
|
|
// },
|
|
// };
|
|
|