Skip to content

Commit c5269cf

Browse files
authored
Merge pull request #394 from ut-code/task/redesign-api
refactor: Redesign api
2 parents 37ab6fd + c7a96b2 commit c5269cf

File tree

5 files changed

+152
-253
lines changed

5 files changed

+152
-253
lines changed

backend/src/app.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,18 @@ app.get("/api/elasticsearch/indices", async (_req, res) => {
5151
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument))
5252

5353
app.post("/api/recipes/search", SearchController.searchRecipes)
54-
app.post("/api/recipes/search/keywords", SearchController.searchRecipesWithKeywords)
55-
app.post("/api/recipes/search/query", SearchController.searchRecipesWithQuery)
54+
app.post("/api/recipes/search/some", SearchController.searchSomeRecipes)
5655

5756
app.get("/api/users/favorites", UserController.getFavorites)
5857
app.post("/api/users/favorites", UserController.addFavorite)
5958
app.delete("/api/users/favorites/:id", UserController.deleteFavorite)
6059
app.get("/api/users", UserController.getUser)
6160

62-
app.post("/api/recipes/:index", RecipeController.recreateIndex)
61+
app.post("/api/recipes/index/recreate", RecipeController.recreateIndex)
6362
app.post("/api/recipes", RecipeController.indexRecipe)
6463
app.post("/api/recipes/bulk", RecipeController.bulkIndexRecipes)
6564
app.delete("/api/recipes/:id", RecipeController.deleteRecipe)
6665
app.post("/api/recipes/new", RecipeController.createRecipe)
67-
app.post("/api/recipes/scrape", RecipeController.scrapeRecipe)
66+
// app.post("/api/recipes/scrape", RecipeController.scrapeRecipe)
6867

6968
export default app

backend/src/controllers/RecipeController.ts

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ class RecipeController {
3737
}
3838
}
3939

40-
recreateIndex = async (req: Request, res: Response): Promise<void> => {
40+
recreateIndex = async (_req: Request, res: Response): Promise<void> => {
4141
try {
42-
const indexName = req.params.index
42+
const indexName = "recipes"
4343
const indexExists = await elasticSearchClient.indices.exists({
4444
index: indexName,
4545
})
@@ -452,59 +452,59 @@ class RecipeController {
452452
}
453453
}
454454

455-
scrapeRecipe = async (req: Request, res: Response): Promise<void> => {
456-
try {
457-
const userFromRequest = await extractUserFromRequest(req)
458-
if (!userFromRequest) {
459-
// res.status(401).json({ error: "Not authorized" })
460-
// return
461-
console.log("Not authorized")
462-
}
455+
// scrapeRecipe = async (req: Request, res: Response): Promise<void> => {
456+
// try {
457+
// const userFromRequest = await extractUserFromRequest(req)
458+
// if (!userFromRequest) {
459+
// // res.status(401).json({ error: "Not authorized" })
460+
// // return
461+
// console.log("Not authorized")
462+
// }
463463

464-
// const { sourceUrl } = req.body
464+
// // const { sourceUrl } = req.body
465465

466-
// const browser = await chromium.launch()
467-
// const context = await browser.newContext()
468-
// const page = await context.newPage()
469-
// // Ref) https://playwright.dev/docs/api/class-page#page-goto
470-
// await page.goto(sourceUrl, { waitUntil: "domcontentloaded" })
466+
// // const browser = await chromium.launch()
467+
// // const context = await browser.newContext()
468+
// // const page = await context.newPage()
469+
// // // Ref) https://playwright.dev/docs/api/class-page#page-goto
470+
// // await page.goto(sourceUrl, { waitUntil: "domcontentloaded" })
471471

472-
// const recipeData = await page.evaluate(() => {
473-
// // FIXME: 複数のタグがある場合に対応させる
474-
// const scriptTag = document.querySelector("script[type='application/ld+json']")
475-
// if (!scriptTag) {
476-
// return null
477-
// }
478-
// const recipeJson = JSON.parse(scriptTag.innerHTML)
479-
// if (recipeJson["@type"] !== "Recipe") {
480-
// return null
481-
// }
482-
// return recipeJson
483-
// })
472+
// // const recipeData = await page.evaluate(() => {
473+
// // // FIXME: 複数のタグがある場合に対応させる
474+
// // const scriptTag = document.querySelector("script[type='application/ld+json']")
475+
// // if (!scriptTag) {
476+
// // return null
477+
// // }
478+
// // const recipeJson = JSON.parse(scriptTag.innerHTML)
479+
// // if (recipeJson["@type"] !== "Recipe") {
480+
// // return null
481+
// // }
482+
// // return recipeJson
483+
// // })
484484

485-
// await browser.close()
485+
// // await browser.close()
486486

487-
// // console.log(recipeData)
488-
// if (!recipeData) {
489-
// res.status(400).json({ error: "Could not find structured recipe data" })
490-
// return
491-
// }
492-
// const recipeResponse = {
493-
// title: recipeData.name,
494-
// description: recipeData.description,
495-
// totalCookingTime: this.convertTotalCookingTimeToMinutes(recipeData.totalTime),
496-
// materials: recipeData.recipeIngredient,
497-
// keywords: this.convertKeywords(recipeData.keywords),
498-
// sourceUrl: sourceUrl,
499-
// foodImageUrl: recipeData.image[0],
500-
// dish: recipeData.recipeCategory,
501-
// }
502-
// res.status(200).json(recipeResponse)
503-
} catch (error) {
504-
console.error(error)
505-
res.status(500).json({ error: "Internal server error" })
506-
}
507-
}
487+
// // // console.log(recipeData)
488+
// // if (!recipeData) {
489+
// // res.status(400).json({ error: "Could not find structured recipe data" })
490+
// // return
491+
// // }
492+
// // const recipeResponse = {
493+
// // title: recipeData.name,
494+
// // description: recipeData.description,
495+
// // totalCookingTime: this.convertTotalCookingTimeToMinutes(recipeData.totalTime),
496+
// // materials: recipeData.recipeIngredient,
497+
// // keywords: this.convertKeywords(recipeData.keywords),
498+
// // sourceUrl: sourceUrl,
499+
// // foodImageUrl: recipeData.image[0],
500+
// // dish: recipeData.recipeCategory,
501+
// // }
502+
// // res.status(200).json(recipeResponse)
503+
// } catch (error) {
504+
// console.error(error)
505+
// res.status(500).json({ error: "Internal server error" })
506+
// }
507+
// }
508508

509509
// eslint-disable-next-line @typescript-eslint/no-explicit-any
510510
private isRecipe = (data: any): data is Recipes => {

backend/src/controllers/SearchController.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,33 @@ class SearchController {
9191
}
9292
}
9393

94-
searchRecipesWithKeywords = async (req: Request, res: Response): Promise<void> => {
94+
searchSomeRecipes = async (_req: Request, res: Response): Promise<void> => {
95+
try {
96+
const result = await elasticSearchClient.search({
97+
index: "recipes",
98+
body: {
99+
query: {
100+
match_all: {},
101+
},
102+
size: 30,
103+
},
104+
})
105+
106+
const hits = result.hits.hits
107+
if (hits.length === 0) {
108+
res.status(404).json({ error: "Not found" })
109+
return
110+
}
111+
const recipes = hits.map((hit) => hit._source)
112+
res.json(recipes)
113+
} catch (error) {
114+
console.error(error)
115+
res.status(500).json({ error: "Internal server error" })
116+
}
117+
}
118+
119+
// TODO: type=keywords
120+
private searchRecipesWithKeywords = async (req: Request, res: Response): Promise<void> => {
95121
try {
96122
const { keywords } = req.body
97123

@@ -122,7 +148,8 @@ class SearchController {
122148
}
123149
}
124150

125-
searchRecipesWithQuery = async (req: Request, res: Response): Promise<void> => {
151+
// TODO: type=query
152+
private searchRecipesWithQuery = async (req: Request, res: Response): Promise<void> => {
126153
try {
127154
const { query } = req.body
128155
const result = await elasticSearchClient.search({

openapi.json

Lines changed: 70 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
"title": {
2525
"type": "string"
2626
},
27+
"sourceUrl": {
28+
"type": "string"
29+
},
2730
"description": {
2831
"type": "string"
2932
},
@@ -36,15 +39,6 @@
3639
"type": "string"
3740
}
3841
},
39-
"keywords": {
40-
"type": "array",
41-
"items": {
42-
"type": "string"
43-
}
44-
},
45-
"sourceUrl": {
46-
"type": "string"
47-
},
4842
"foodImageUrl": {
4943
"type": "string"
5044
},
@@ -56,10 +50,6 @@
5650
},
5751
"cuisine": {
5852
"type": "string"
59-
},
60-
"createdAt": {
61-
"type": "string",
62-
"format": "date-time"
6353
}
6454
},
6555
"required": [
@@ -121,9 +111,73 @@
121111
}
122112
}
123113
},
124-
"/api/recipes": {
114+
"/api/elasticsearch/indices": {
115+
"get": {
116+
"description": "",
117+
"responses": {
118+
"200": {
119+
"description": "OK"
120+
},
121+
"500": {
122+
"description": "Internal Server Error"
123+
}
124+
}
125+
}
126+
},
127+
"/api/recipes/index/recreate": {
125128
"post": {
126129
"description": "",
130+
"responses": {
131+
"200": {
132+
"description": "OK"
133+
},
134+
"500": {
135+
"description": "Internal Server Error"
136+
}
137+
}
138+
}
139+
},
140+
"/api/recipes": {
141+
"post": {
142+
"description": "index recipe",
143+
"requestBody": {
144+
"content": {
145+
"application/json": {
146+
"schema": {
147+
"type": "object",
148+
"properties": {
149+
"recipe": {
150+
"example": {
151+
"id": 1,
152+
"title": "テストタイトル",
153+
"description": "豚肉の生姜焼きのレシピです。",
154+
"totalCookingTime": 30,
155+
"materials": ["豚肉", "生姜", "醤油", "みりん", "砂糖"],
156+
"sourceUrl": "https://google.com",
157+
"foodImageUrl": "https://google.com",
158+
"dish": "主菜",
159+
"category": "和食",
160+
"cuisine": "日本料理"
161+
}
162+
}
163+
}
164+
}
165+
}
166+
}
167+
},
168+
"responses": {
169+
"201": {
170+
"description": "Created"
171+
},
172+
"500": {
173+
"description": "Internal Server Error"
174+
}
175+
}
176+
}
177+
},
178+
"/api/recipes/bulk": {
179+
"post": {
180+
"description": "index recipe in bulk",
127181
"requestBody": {
128182
"content": {
129183
"application/json": {
@@ -150,7 +204,7 @@
150204
},
151205
"/api/recipes/{id}": {
152206
"delete": {
153-
"description": "",
207+
"description": "delete recipe",
154208
"parameters": [
155209
{
156210
"name": "id",
@@ -207,53 +261,9 @@
207261
}
208262
}
209263
},
210-
"/api/recipes/search/keywords": {
211-
"post": {
212-
"description": "",
213-
"requestBody": {
214-
"content": {
215-
"application/json": {
216-
"schema": {
217-
"type": "object",
218-
"properties": {
219-
"keywords": {
220-
"example": "any"
221-
}
222-
}
223-
}
224-
}
225-
}
226-
},
227-
"responses": {
228-
"200": {
229-
"description": "OK"
230-
},
231-
"404": {
232-
"description": "Not Found"
233-
},
234-
"500": {
235-
"description": "Internal Server Error"
236-
}
237-
}
238-
}
239-
},
240-
"/api/recipes/search/query": {
264+
"/api/recipes/search/some": {
241265
"post": {
242266
"description": "",
243-
"requestBody": {
244-
"content": {
245-
"application/json": {
246-
"schema": {
247-
"type": "object",
248-
"properties": {
249-
"query": {
250-
"example": "any"
251-
}
252-
}
253-
}
254-
}
255-
}
256-
},
257267
"responses": {
258268
"200": {
259269
"description": "OK"

0 commit comments

Comments
 (0)