diff options
author | 2024-02-10 18:37:40 -0700 | |
---|---|---|
committer | 2024-02-13 06:59:44 +0530 | |
commit | e1c47572a6235fd8fd20af888ac3a11c7ae1369d (patch) | |
tree | 2dccff36a441916d7014037cef3f7ce84a790cad /app/Controllers/Http/RecipeController.ts | |
parent | refactor: project maintenance (diff) | |
download | ferdium-server-e1c47572a6235fd8fd20af888ac3a11c7ae1369d.tar.gz ferdium-server-e1c47572a6235fd8fd20af888ac3a11c7ae1369d.tar.zst ferdium-server-e1c47572a6235fd8fd20af888ac3a11c7ae1369d.zip |
updates
Diffstat (limited to 'app/Controllers/Http/RecipeController.ts')
-rw-r--r-- | app/Controllers/Http/RecipeController.ts | 165 |
1 files changed, 94 insertions, 71 deletions
diff --git a/app/Controllers/Http/RecipeController.ts b/app/Controllers/Http/RecipeController.ts index e43bcf8..d30c59f 100644 --- a/app/Controllers/Http/RecipeController.ts +++ b/app/Controllers/Http/RecipeController.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import type { HttpContext } from '@adonisjs/core/http' | 1 | import type { HttpContext } from '@adonisjs/core/http'; |
2 | import fs from 'fs-extra' | 2 | import fs from 'fs-extra'; |
3 | import { app } from '@adonisjs/core/services/app' | 3 | import { app } from '@adonisjs/core/services/app'; |
4 | import path from 'node:path' | 4 | import path from 'node:path'; |
5 | import Recipe from '#app/Models/Recipe' | 5 | import Recipe from '#app/Models/Recipe'; |
6 | import { isCreationEnabled } from '#config/app' | 6 | import { isCreationEnabled } from '#config/app'; |
7 | import { validator, schema, rules } from '@adonisjs/validator' | 7 | import { validator, schema, rules } from '@adonisjs/validator'; |
8 | import targz from 'targz' | 8 | import targz from 'targz'; |
9 | import semver from 'semver' | 9 | import semver from 'semver'; |
10 | import Drive from '@ioc:Adonis/Core/Drive' | 10 | import Drive from '@ioc:Adonis/Core/Drive'; |
11 | 11 | ||
12 | // TODO: This file needs to be refactored and cleaned up to include types | 12 | // TODO: This file needs to be refactored and cleaned up to include types |
13 | 13 | ||
@@ -18,17 +18,17 @@ const createSchema = schema.create({ | |||
18 | // author: 'required|accepted', | 18 | // author: 'required|accepted', |
19 | author: schema.string(), | 19 | author: schema.string(), |
20 | svg: schema.string([rules.url()]), | 20 | svg: schema.string([rules.url()]), |
21 | }) | 21 | }); |
22 | 22 | ||
23 | const searchSchema = schema.create({ | 23 | const searchSchema = schema.create({ |
24 | needle: schema.string(), | 24 | needle: schema.string(), |
25 | }) | 25 | }); |
26 | 26 | ||
27 | const downloadSchema = schema.create({ | 27 | const downloadSchema = schema.create({ |
28 | // TODO: Check if this is correct | 28 | // TODO: Check if this is correct |
29 | // recipe: 'required|accepted', | 29 | // recipe: 'required|accepted', |
30 | recipe: schema.string(), | 30 | recipe: schema.string(), |
31 | }) | 31 | }); |
32 | 32 | ||
33 | const compress = (src: string, dest: string) => | 33 | const compress = (src: string, dest: string) => |
34 | new Promise((resolve, reject) => { | 34 | new Promise((resolve, reject) => { |
@@ -37,30 +37,34 @@ const compress = (src: string, dest: string) => | |||
37 | src, | 37 | src, |
38 | dest, | 38 | dest, |
39 | }, | 39 | }, |
40 | (err) => { | 40 | err => { |
41 | if (err) { | 41 | if (err) { |
42 | reject(err) | 42 | reject(err); |
43 | } else { | 43 | } else { |
44 | resolve(dest) | 44 | resolve(dest); |
45 | } | 45 | } |
46 | } | 46 | }, |
47 | ) | 47 | ); |
48 | }) | 48 | }); |
49 | 49 | ||
50 | export default class RecipesController { | 50 | export default class RecipesController { |
51 | // List official and custom recipes | 51 | // List official and custom recipes |
52 | public async list({ response }: HttpContext) { | 52 | public async list({ response }: HttpContext) { |
53 | const officialRecipes = fs.readJsonSync(path.join(app.appRoot, 'recipes', 'all.json')) | 53 | const officialRecipes = fs.readJsonSync( |
54 | const customRecipesArray = await Recipe.all() | 54 | path.join(app.appRoot, 'recipes', 'all.json'), |
55 | const customRecipes = customRecipesArray.map((recipe) => ({ | 55 | ); |
56 | const customRecipesArray = await Recipe.all(); | ||
57 | const customRecipes = customRecipesArray.map(recipe => ({ | ||
56 | id: recipe.recipeId, | 58 | id: recipe.recipeId, |
57 | name: recipe.name, | 59 | name: recipe.name, |
58 | ...(typeof recipe.data === 'string' ? JSON.parse(recipe.data) : recipe.data), | 60 | ...(typeof recipe.data === 'string' |
59 | })) | 61 | ? JSON.parse(recipe.data) |
62 | : recipe.data), | ||
63 | })); | ||
60 | 64 | ||
61 | const recipes = [...officialRecipes, ...customRecipes] | 65 | const recipes = [...officialRecipes, ...customRecipes]; |
62 | 66 | ||
63 | return response.send(recipes) | 67 | return response.send(recipes); |
64 | } | 68 | } |
65 | 69 | ||
66 | // TODO: Test this endpoint | 70 | // TODO: Test this endpoint |
@@ -68,45 +72,52 @@ export default class RecipesController { | |||
68 | public async create({ request, response }: HttpContext) { | 72 | public async create({ request, response }: HttpContext) { |
69 | // Check if recipe creation is enabled | 73 | // Check if recipe creation is enabled |
70 | if (isCreationEnabled === 'false') { | 74 | if (isCreationEnabled === 'false') { |
71 | return response.send("This server doesn't allow the creation of new recipes.") | 75 | return response.send( |
76 | "This server doesn't allow the creation of new recipes.", | ||
77 | ); | ||
72 | } | 78 | } |
73 | 79 | ||
74 | // Validate user input | 80 | // Validate user input |
75 | let data | 81 | let data; |
76 | try { | 82 | try { |
77 | data = await request.validate({ schema: createSchema }) | 83 | data = await request.validate({ schema: createSchema }); |
78 | } catch (error) { | 84 | } catch (error) { |
79 | return response.status(401).send({ | 85 | return response.status(401).send({ |
80 | message: 'Invalid POST arguments', | 86 | message: 'Invalid POST arguments', |
81 | messages: error.messages, | 87 | messages: error.messages, |
82 | status: 401, | 88 | status: 401, |
83 | }) | 89 | }); |
84 | } | 90 | } |
85 | 91 | ||
86 | if (!data.id) { | 92 | if (!data.id) { |
87 | return response.send('Please provide an ID') | 93 | return response.send('Please provide an ID'); |
88 | } | 94 | } |
89 | 95 | ||
90 | // Check for invalid characters | 96 | // Check for invalid characters |
91 | if (/\.+/.test(data.id) || /\/+/.test(data.id)) { | 97 | if (/\.+/.test(data.id) || /\/+/.test(data.id)) { |
92 | return response.send('Invalid recipe name. Your recipe name may not contain "." or "/"') | 98 | return response.send( |
99 | 'Invalid recipe name. Your recipe name may not contain "." or "/"', | ||
100 | ); | ||
93 | } | 101 | } |
94 | 102 | ||
95 | // Clear temporary recipe folder | 103 | // Clear temporary recipe folder |
96 | await fs.emptyDir(app.tmpPath('recipe')) | 104 | await fs.emptyDir(app.tmpPath('recipe')); |
97 | 105 | ||
98 | // Move uploaded files to temporary path | 106 | // Move uploaded files to temporary path |
99 | const files = request.file('files') | 107 | const files = request.file('files'); |
100 | if (!files) { | 108 | if (!files) { |
101 | return response.abort('Error processsing files.') | 109 | return response.abort('Error processsing files.'); |
102 | } | 110 | } |
103 | await files.move(app.tmpPath('recipe')) | 111 | await files.move(app.tmpPath('recipe')); |
104 | 112 | ||
105 | // Compress files to .tar.gz file | 113 | // Compress files to .tar.gz file |
106 | const source = app.tmpPath('recipe') | 114 | const source = app.tmpPath('recipe'); |
107 | const destination = path.join(app.appRoot, `/recipes/archives/${data.id}.tar.gz`) | 115 | const destination = path.join( |
116 | app.appRoot, | ||
117 | `/recipes/archives/${data.id}.tar.gz`, | ||
118 | ); | ||
108 | 119 | ||
109 | compress(source, destination) | 120 | compress(source, destination); |
110 | 121 | ||
111 | // Create recipe in db | 122 | // Create recipe in db |
112 | await Recipe.create({ | 123 | await Recipe.create({ |
@@ -121,47 +132,55 @@ export default class RecipesController { | |||
121 | svg: data.svg, | 132 | svg: data.svg, |
122 | }, | 133 | }, |
123 | }), | 134 | }), |
124 | }) | 135 | }); |
125 | 136 | ||
126 | return response.send('Created new recipe') | 137 | return response.send('Created new recipe'); |
127 | } | 138 | } |
128 | 139 | ||
129 | // Search official and custom recipes | 140 | // Search official and custom recipes |
130 | public async search({ request, response }: HttpContext) { | 141 | public async search({ request, response }: HttpContext) { |
131 | // Validate user input | 142 | // Validate user input |
132 | let data | 143 | let data; |
133 | try { | 144 | try { |
134 | data = await request.validate({ schema: searchSchema }) | 145 | data = await request.validate({ schema: searchSchema }); |
135 | } catch (error) { | 146 | } catch (error) { |
136 | return response.status(401).send({ | 147 | return response.status(401).send({ |
137 | message: 'Please provide a needle', | 148 | message: 'Please provide a needle', |
138 | messages: error.messages, | 149 | messages: error.messages, |
139 | status: 401, | 150 | status: 401, |
140 | }) | 151 | }); |
141 | } | 152 | } |
142 | 153 | ||
143 | const { needle } = data | 154 | const { needle } = data; |
144 | 155 | ||
145 | // Get results | 156 | // Get results |
146 | let results | 157 | let results; |
147 | 158 | ||
148 | if (needle === 'ferdium:custom') { | 159 | if (needle === 'ferdium:custom') { |
149 | const dbResults = await Recipe.all() | 160 | const dbResults = await Recipe.all(); |
150 | results = dbResults.map((recipe) => ({ | 161 | results = dbResults.map(recipe => ({ |
151 | id: recipe.recipeId, | 162 | id: recipe.recipeId, |
152 | name: recipe.name, | 163 | name: recipe.name, |
153 | ...(typeof recipe.data === 'string' ? JSON.parse(recipe.data) : recipe.data), | 164 | ...(typeof recipe.data === 'string' |
154 | })) | 165 | ? JSON.parse(recipe.data) |
166 | : recipe.data), | ||
167 | })); | ||
155 | } else { | 168 | } else { |
156 | const localResultsArray = await Recipe.query().where('name', 'LIKE', `%${needle}%`) | 169 | const localResultsArray = await Recipe.query().where( |
157 | results = localResultsArray.map((recipe) => ({ | 170 | 'name', |
171 | 'LIKE', | ||
172 | `%${needle}%`, | ||
173 | ); | ||
174 | results = localResultsArray.map(recipe => ({ | ||
158 | id: recipe.recipeId, | 175 | id: recipe.recipeId, |
159 | name: recipe.name, | 176 | name: recipe.name, |
160 | ...(typeof recipe.data === 'string' ? JSON.parse(recipe.data) : recipe.data), | 177 | ...(typeof recipe.data === 'string' |
161 | })) | 178 | ? JSON.parse(recipe.data) |
179 | : recipe.data), | ||
180 | })); | ||
162 | } | 181 | } |
163 | 182 | ||
164 | return response.send(results) | 183 | return response.send(results); |
165 | } | 184 | } |
166 | 185 | ||
167 | public popularRecipes({ response }: HttpContext) { | 186 | public popularRecipes({ response }: HttpContext) { |
@@ -169,63 +188,67 @@ export default class RecipesController { | |||
169 | fs | 188 | fs |
170 | .readJsonSync(path.join(app.appRoot, 'recipes', 'all.json')) | 189 | .readJsonSync(path.join(app.appRoot, 'recipes', 'all.json')) |
171 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | 190 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
172 | .filter((recipe: any) => recipe.featured) | 191 | .filter((recipe: any) => recipe.featured), |
173 | ) | 192 | ); |
174 | } | 193 | } |
175 | 194 | ||
176 | // TODO: test this endpoint | 195 | // TODO: test this endpoint |
177 | public update({ request, response }: HttpContext) { | 196 | public update({ request, response }: HttpContext) { |
178 | const updates = [] | 197 | const updates = []; |
179 | const recipes = request.all() | 198 | const recipes = request.all(); |
180 | const allJson = fs.readJsonSync(path.join(app.appRoot, 'recipes', 'all.json')) | 199 | const allJson = fs.readJsonSync( |
200 | path.join(app.appRoot, 'recipes', 'all.json'), | ||
201 | ); | ||
181 | 202 | ||
182 | for (const recipe of Object.keys(recipes)) { | 203 | for (const recipe of Object.keys(recipes)) { |
183 | const version = recipes[recipe] | 204 | const version = recipes[recipe]; |
184 | 205 | ||
185 | // Find recipe in local recipe repository | 206 | // Find recipe in local recipe repository |
186 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | 207 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
187 | const localRecipe = allJson.find((r: any) => r.id === recipe) | 208 | const localRecipe = allJson.find((r: any) => r.id === recipe); |
188 | if (localRecipe && semver.lt(version, localRecipe.version)) { | 209 | if (localRecipe && semver.lt(version, localRecipe.version)) { |
189 | updates.push(recipe) | 210 | updates.push(recipe); |
190 | } | 211 | } |
191 | } | 212 | } |
192 | 213 | ||
193 | return response.send(updates) | 214 | return response.send(updates); |
194 | } | 215 | } |
195 | 216 | ||
196 | // TODO: test this endpoint | 217 | // TODO: test this endpoint |
197 | // Download a recipe | 218 | // Download a recipe |
198 | public async download({ response, params }: HttpContext) { | 219 | public async download({ response, params }: HttpContext) { |
199 | // Validate user input | 220 | // Validate user input |
200 | let data | 221 | let data; |
201 | try { | 222 | try { |
202 | data = await validator.validate({ | 223 | data = await validator.validate({ |
203 | data: params, | 224 | data: params, |
204 | schema: downloadSchema, | 225 | schema: downloadSchema, |
205 | }) | 226 | }); |
206 | } catch (error) { | 227 | } catch (error) { |
207 | return response.status(401).send({ | 228 | return response.status(401).send({ |
208 | message: 'Please provide a recipe ID', | 229 | message: 'Please provide a recipe ID', |
209 | messages: error.messages, | 230 | messages: error.messages, |
210 | status: 401, | 231 | status: 401, |
211 | }) | 232 | }); |
212 | } | 233 | } |
213 | 234 | ||
214 | const service = data.recipe | 235 | const service = data.recipe; |
215 | 236 | ||
216 | // Check for invalid characters | 237 | // Check for invalid characters |
217 | if (/\.+/.test(service) || /\/+/.test(service)) { | 238 | if (/\.+/.test(service) || /\/+/.test(service)) { |
218 | return response.send('Invalid recipe name') | 239 | return response.send('Invalid recipe name'); |
219 | } | 240 | } |
220 | 241 | ||
221 | // Check if recipe exists in recipes folder | 242 | // Check if recipe exists in recipes folder |
222 | if (await Drive.exists(`${service}.tar.gz`)) { | 243 | if (await Drive.exists(`${service}.tar.gz`)) { |
223 | return response.type('.tar.gz').send(await Drive.get(`${service}.tar.gz`)) | 244 | return response |
245 | .type('.tar.gz') | ||
246 | .send(await Drive.get(`${service}.tar.gz`)); | ||
224 | } | 247 | } |
225 | 248 | ||
226 | return response.status(400).send({ | 249 | return response.status(400).send({ |
227 | message: 'Recipe not found', | 250 | message: 'Recipe not found', |
228 | code: 'recipe-not-found', | 251 | code: 'recipe-not-found', |
229 | }) | 252 | }); |
230 | } | 253 | } |
231 | } | 254 | } |