aboutsummaryrefslogtreecommitdiffstats
path: root/app/Controllers/Http/RecipeController.ts
diff options
context:
space:
mode:
Diffstat (limited to 'app/Controllers/Http/RecipeController.ts')
-rw-r--r--app/Controllers/Http/RecipeController.ts165
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 @@
1import type { HttpContext } from '@adonisjs/core/http' 1import type { HttpContext } from '@adonisjs/core/http';
2import fs from 'fs-extra' 2import fs from 'fs-extra';
3import { app } from '@adonisjs/core/services/app' 3import { app } from '@adonisjs/core/services/app';
4import path from 'node:path' 4import path from 'node:path';
5import Recipe from '#app/Models/Recipe' 5import Recipe from '#app/Models/Recipe';
6import { isCreationEnabled } from '#config/app' 6import { isCreationEnabled } from '#config/app';
7import { validator, schema, rules } from '@adonisjs/validator' 7import { validator, schema, rules } from '@adonisjs/validator';
8import targz from 'targz' 8import targz from 'targz';
9import semver from 'semver' 9import semver from 'semver';
10import Drive from '@ioc:Adonis/Core/Drive' 10import 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
23const searchSchema = schema.create({ 23const searchSchema = schema.create({
24 needle: schema.string(), 24 needle: schema.string(),
25}) 25});
26 26
27const downloadSchema = schema.create({ 27const 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
33const compress = (src: string, dest: string) => 33const 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
50export default class RecipesController { 50export 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}