aboutsummaryrefslogtreecommitdiffstats
path: root/src/server/app/Controllers/Http/RecipeController.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/app/Controllers/Http/RecipeController.js')
-rw-r--r--src/server/app/Controllers/Http/RecipeController.js207
1 files changed, 207 insertions, 0 deletions
diff --git a/src/server/app/Controllers/Http/RecipeController.js b/src/server/app/Controllers/Http/RecipeController.js
new file mode 100644
index 000000000..5ed21122a
--- /dev/null
+++ b/src/server/app/Controllers/Http/RecipeController.js
@@ -0,0 +1,207 @@
1
2const Recipe = use('App/Models/Recipe');
3const Helpers = use('Helpers');
4const Drive = use('Drive');
5const {
6 validateAll,
7} = use('Validator');
8const Env = use('Env');
9
10const fetch = require('node-fetch');
11const targz = require('targz');
12const path = require('path');
13const fs = require('fs-extra');
14
15const compress = (src, dest) => new Promise((resolve, reject) => {
16 targz.compress({
17 src,
18 dest,
19 }, (err) => {
20 if (err) {
21 reject(err);
22 } else {
23 resolve(dest);
24 }
25 });
26});
27
28class RecipeController {
29 // List official and custom recipes
30 async list({
31 response,
32 }) {
33 const officialRecipes = JSON.parse(await (await fetch('https://api.franzinfra.com/v1/recipes')).text());
34 const customRecipesArray = (await Recipe.all()).rows;
35 const customRecipes = customRecipesArray.map(recipe => ({
36 id: recipe.recipeId,
37 name: recipe.name,
38 ...JSON.parse(recipe.data),
39 }));
40
41 const recipes = [
42 ...officialRecipes,
43 ...customRecipes,
44 ];
45
46 return response.send(recipes);
47 }
48
49 // Create a new recipe using the new.html page
50 async create({
51 request,
52 response,
53 }) {
54 // Check if recipe creation is enabled
55 if (Env.get('IS_CREATION_ENABLED') == 'false') { // eslint-disable-line eqeqeq
56 return response.send('This server doesn\'t allow the creation of new recipes.');
57 }
58
59 // Validate user input
60 const validation = await validateAll(request.all(), {
61 name: 'required|string',
62 id: 'required|unique:recipes,recipeId',
63 author: 'required|accepted',
64 png: 'required|url',
65 svg: 'required|url',
66 });
67 if (validation.fails()) {
68 return response.status(401).send({
69 message: 'Invalid POST arguments',
70 messages: validation.messages(),
71 status: 401,
72 });
73 }
74
75 const data = request.all();
76
77 if (!data.id) {
78 return response.send('Please provide an ID');
79 }
80
81 // Check for invalid characters
82 if (/\.{1,}/.test(data.id) || /\/{1,}/.test(data.id)) {
83 return response.send('Invalid recipe name. Your recipe name may not contain "." or "/"');
84 }
85
86 // Clear temporary recipe folder
87 await fs.emptyDir(Helpers.tmpPath('recipe'));
88
89 // Move uploaded files to temporary path
90 const files = request.file('files');
91 await files.moveAll(Helpers.tmpPath('recipe'));
92
93 // Compress files to .tar.gz file
94 const source = Helpers.tmpPath('recipe');
95 const destination = path.join(Helpers.appRoot(), `/recipes/${data.id}.tar.gz`);
96
97 compress(
98 source,
99 destination,
100 );
101
102 // Create recipe in db
103 await Recipe.create({
104 name: data.name,
105 recipeId: data.id,
106 data: JSON.stringify({
107 author: data.author,
108 featured: false,
109 version: '1.0.0',
110 icons: {
111 png: data.png,
112 svg: data.svg,
113 },
114 }),
115 });
116
117 return response.send('Created new recipe');
118 }
119
120 // Search official and custom recipes
121 async search({
122 request,
123 response,
124 }) {
125 // Validate user input
126 const validation = await validateAll(request.all(), {
127 needle: 'required',
128 });
129 if (validation.fails()) {
130 return response.status(401).send({
131 message: 'Please provide a needle',
132 messages: validation.messages(),
133 status: 401,
134 });
135 }
136
137 const needle = request.input('needle');
138
139 // Get results
140 let results;
141
142 if (needle === 'ferdi:custom') {
143 const dbResults = (await Recipe.all()).toJSON();
144 results = dbResults.map(recipe => ({
145 id: recipe.recipeId,
146 name: recipe.name,
147 ...JSON.parse(recipe.data),
148 }));
149 } else {
150 let remoteResults = [];
151 if (Env.get('CONNECT_WITH_FRANZ') == 'true') { // eslint-disable-line eqeqeq
152 remoteResults = JSON.parse(await (await fetch(`https://api.franzinfra.com/v1/recipes/search?needle=${encodeURIComponent(needle)}`)).text());
153 }
154 const localResultsArray = (await Recipe.query().where('name', 'LIKE', `%${needle}%`).fetch()).toJSON();
155 const localResults = localResultsArray.map(recipe => ({
156 id: recipe.recipeId,
157 name: recipe.name,
158 ...JSON.parse(recipe.data),
159 }));
160
161 results = [
162 ...localResults,
163 ...remoteResults || [],
164 ];
165 }
166
167 return response.send(results);
168 }
169
170 // Download a recipe
171 async download({
172 response,
173 params,
174 }) {
175 // Validate user input
176 const validation = await validateAll(params, {
177 recipe: 'required|accepted',
178 });
179 if (validation.fails()) {
180 return response.status(401).send({
181 message: 'Please provide a recipe ID',
182 messages: validation.messages(),
183 status: 401,
184 });
185 }
186
187 const service = params.recipe;
188
189 // Check for invalid characters
190 if (/\.{1,}/.test(service) || /\/{1,}/.test(service)) {
191 return response.send('Invalid recipe name');
192 }
193
194 // Check if recipe exists in recipes folder
195 if (await Drive.exists(`${service}.tar.gz`)) {
196 return response.send(await Drive.get(`${service}.tar.gz`));
197 } if (Env.get('CONNECT_WITH_FRANZ') == 'true') { // eslint-disable-line eqeqeq
198 return response.redirect(`https://api.franzinfra.com/v1/recipes/download/${service}`);
199 }
200 return response.status(400).send({
201 message: 'Recipe not found',
202 code: 'recipe-not-found',
203 });
204 }
205}
206
207module.exports = RecipeController;