import config from '../config'; import * as Errors from './Errors'; class Api { constructor() { } baseUrl() { return config.baseApiUrl; } url(path) { return this.baseUrl() + path; } checkStatus(response) { if (response.status == 204) { return null; } else if (response.ok) { return response.json(); } else if (response.status === 404) { throw new Errors.ApiNotFoundError(response.statusText, response); } else if (response.status === 422) { return response.json().then(json => { throw new Errors.ApiValidationError(null, response, json) }, jsonErr => { throw new Errors.ApiValidationError(null, response, null) }); } else { throw new Errors.ApiServerError(response.statusText || "Unknown Server Error", response); } } performRequest(url, method, params = {}, headers = {}) { const hasBody = Object.keys(params || {}).length !== 0; const reqHeaders = new Headers(); reqHeaders.append('Accept', 'application/json'); reqHeaders.append('Content-Type', 'application/json'); for (let key in headers) { reqHeaders.append(key, headers[key]); } const opts = { headers: reqHeaders, method: method, credentials: "same-origin" }; if (hasBody) { opts.body = JSON.stringify(params); } return fetch(url, opts).then(this.checkStatus); } objectToUrlParams(obj, queryParams = [], prefixes = []) { for (let key in obj) { const val = obj[key]; const paramName = prefixes.join("[") + "[".repeat(Math.min(prefixes.length, 1)) + encodeURIComponent(key) + "]".repeat(prefixes.length); if (Array.isArray(val)) { for (let x of val) { queryParams.push(paramName + "[]=" + (x === null ? '' : encodeURIComponent(x))); } } else if (typeof(val) === "object") { this.objectToUrlParams(val, queryParams, prefixes.concat([key])); } else { queryParams.push(paramName + "=" + (val === null ? '' : encodeURIComponent(val))); } } return queryParams; } buildGetUrl(url, params = {}) { const queryParams = this.objectToUrlParams(params); if (queryParams.length) { url = url + "?" + queryParams.join("&"); } return url; } get(url, params = {}) { url = this.buildGetUrl(url, params); return this.performRequest(url, "GET"); } cacheFirstGet(url, params = {}, dataHandler) { url = this.buildGetUrl(url, params); let networkDataReceived = false; const networkUpdate = this.performRequest(url, "GET", {}, {"Cache-Then-Network": "true"}) .then(data => { networkDataReceived = true; return dataHandler(data); }); return caches.match(url) .then(response => { if (!response) throw Error("No data"); return response.json(); }) .then(data => { // don't overwrite newer network data if (!networkDataReceived) { dataHandler(data); } }) .catch(function() { // we didn't get cached data, the network is our last hope: return networkUpdate; }); } post(url, params = {}) { return this.performRequest(url, "POST", params); } patch(url, params = {}) { return this.performRequest(url, "PATCH", params); } del(url, params = {}) { return this.performRequest(url, "DELETE", params); } getRecipeList(page, per, sortColumn, sortDirection, name, tags, dataHandler) { const params = { criteria: { page: page || null, per: per || null, sort_column: sortColumn || null, sort_direction: sortDirection || null, name: name || null, tags: tags || null } }; return this.cacheFirstGet("/recipes", params, dataHandler); } getRecipe(id, scale = null, system = null, unit = null, dataHandler) { const params = { scale, system, unit }; return this.cacheFirstGet("/recipes/" + id, params, dataHandler); } buildRecipeParams(recipe) { const params = { recipe: { name: recipe.name, description: recipe.description, source: recipe.source, yields: recipe.yields, total_time: recipe.total_time, active_time: recipe.active_time, step_text: recipe.step_text, tag_names: recipe.tags, recipe_ingredients_attributes: recipe.ingredients.map(i => { if (i._destroy) { return { id: i.id, _destroy: true }; } else { return { id: i.id, name: i.name, ingredient_id: i.ingredient_id, quantity: i.quantity, units: i.units, preparation: i.preparation, sort_order: i.sort_order }; } }) } }; return params; } patchRecipe(recipe) { return this.patch("/recipes/" + recipe.id, this.buildRecipeParams(recipe)); } postRecipe(recipe) { return this.post("/recipes/", this.buildRecipeParams(recipe)); } deleteRecipe(id) { return this.del("/recipes/" + id); } postPreviewSteps(step_text) { const params = { step_text: step_text }; return this.post("/recipes/preview_steps", params); } getSearchIngredients(query) { const params = { query: query }; return this.get("/ingredients/search", params); } getCalculate(input, output_unit, density) { const params = { input, output_unit, density }; return this.get("/calculator/calculate", params); } getIngredientList(page, per, name) { const params = { page, per, name }; return this.get("/ingredients/", params); } getIngredient(id) { return this.get("/ingredients/" + id); } buildIngredientParams(ingredient) { return { ingredient: { name: ingredient.name, notes: ingredient.notes, ndbn: ingredient.ndbn, density: ingredient.density, water: ingredient.water, ash: ingredient.ash, protein: ingredient.protein, kcal: ingredient.kcal, fiber: ingredient.fiber, sugar: ingredient.sugar, carbohydrates: ingredient.carbohydrates, calcium: ingredient.calcium, iron: ingredient.iron, magnesium: ingredient.magnesium, phosphorus: ingredient.phosphorus, potassium: ingredient.potassium, sodium: ingredient.sodium, zinc: ingredient.zinc, copper: ingredient.copper, manganese: ingredient.manganese, vit_c: ingredient.vit_c, vit_b6: ingredient.vit_b6, vit_b12: ingredient.vit_b12, vit_a: ingredient.vit_a, vit_e: ingredient.vit_e, vit_d: ingredient.vit_d, vit_k: ingredient.vit_k, cholesterol: ingredient.cholesterol, lipids: ingredient.lipids, ingredient_units_attributes: ingredient.ingredient_units.map(iu => { if (iu._destroy) { return { id: iu.id, _destroy: true }; } else { return { id: iu.id, name: iu.name, gram_weight: iu.gram_weight }; } }) } } } postIngredient(ingredient) { return this.post("/ingredients/", this.buildIngredientParams(ingredient)); } patchIngredient(ingredient) { return this.patch("/ingredients/" + ingredient.id, this.buildIngredientParams(ingredient)); } deleteIngredient(id) { return this.del("/ingredients/" + id); } postIngredientSelectNdbn(ingredient) { const url = ingredient.id ? "/ingredients/" + ingredient.id + "/select_ndbn" : "/ingredients/select_ndbn"; return this.post(url, this.buildIngredientParams(ingredient)); } getUsdaFoodSearch(query) { return this.get("/ingredients/usda_food_search", {query: query}); } getNoteList() { return this.get("/notes/"); } postNote(note) { const params = { content: note.content }; return this.post("/notes/", params); } deleteNote(note) { return this.del("/notes/" + note.id); } getLogList(page, per) { const params = { page, per }; return this.get("/logs", params); } getLog(id) { return this.get("/logs/" + id); } buildLogParams(log) { const recParams = this.buildRecipeParams(log.recipe); return { log: { date: log.date, rating: log.rating, notes: log.notes, source_recipe_id: log.source_recipe_id, recipe_attributes: recParams.recipe } }; } postLog(log) { const params = this.buildLogParams(log); const rec = params.log.recipe_attributes; if (rec && rec.recipe_ingredients_attributes) { rec.recipe_ingredients_attributes.forEach(ri => ri.id = null); } return this.post("/recipes/" + log.original_recipe_id + "/logs/", params); } patchLog(log) { return this.patch("/logs/" + log.id, this.buildLogParams(log)); } getTaskLists(dataHandler) { return this.cacheFirstGet("/task_lists/", {}, dataHandler); } buildTaskListParams(taskList) { return { task_list: { name: taskList.name } }; } postTaskList(taskList) { const params = this.buildTaskListParams(taskList); return this.post("/task_lists/", params); } patchTaskList(taskList) { const params = this.buildTaskListParams(taskList); return this.patch(`/task_lists/${taskList.id}`, params); } deleteTaskList(taskList) { return this.del(`/task_lists/${taskList.id}`); } buildTaskItemParams(taskItem) { return { task_item: { name: taskItem.name, quantity: taskItem.quantity, completed: taskItem.completed } } } postTaskItem(listId, taskItem) { const params = this.buildTaskItemParams(taskItem); return this.post(`/task_lists/${listId}/task_items`, params); } patchTaskItem(listId, taskItem) { const params = this.buildTaskItemParams(taskItem); return this.patch(`/task_lists/${listId}/task_items/${taskItem.id}`, params); } deleteTaskItems(listId, taskItems) { const params = { ids: taskItems.map(i => i.id) }; return this.del(`/task_lists/${listId}/task_items/`, params); } completeTaskItems(listId, taskItems, invert = false) { const params = { ids: taskItems.map(i => i.id) }; if (invert === true) { params.invert = true; } return this.patch(`/task_lists/${listId}/task_items/complete`, params); } getAdminUserList() { return this.get("/admin/users"); } postUser(userObj) { const params = { user: { username: userObj.username, full_name: userObj.full_name, email: userObj.email, password: userObj.password, password_confirmation: userObj.password_confirmation } }; return this.post("/user/", params); } patchUser(userObj) { const params = { user: { username: userObj.username, full_name: userObj.full_name, email: userObj.email, password: userObj.password, password_confirmation: userObj.password_confirmation } }; return this.patch("/user/", params); } postLogin(username, password) { const params = { username: username, password: password }; return this.post("/login", params); } getLogout() { return this.get("/logout"); } getCurrentUser() { return this.get("/user") } } const api = new Api(); export default api;