aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar vantezzen <hello@vantezzen.io>2019-10-18 21:43:42 +0200
committerLibravatar vantezzen <hello@vantezzen.io>2019-10-18 21:43:42 +0200
commit1cfff4a4324e130aa7579ea8694438ade686dd55 (patch)
tree7f50f0142283aa1f1e41084dd8efa243ba91b8c0
parentFix lint (diff)
downloadferdium-app-1cfff4a4324e130aa7579ea8694438ade686dd55.tar.gz
ferdium-app-1cfff4a4324e130aa7579ea8694438ade686dd55.tar.zst
ferdium-app-1cfff4a4324e130aa7579ea8694438ade686dd55.zip
Move internal server to submodule
-rw-r--r--src/server/.editorconfig13
-rw-r--r--src/server/.eslintrc.js22
-rw-r--r--src/server/.gitattributes2
-rw-r--r--src/server/.gitignore19
-rw-r--r--src/server/README.md17
-rw-r--r--src/server/ace21
-rw-r--r--src/server/app/Controllers/Http/RecipeController.js119
-rw-r--r--src/server/app/Controllers/Http/ServiceController.js211
-rw-r--r--src/server/app/Controllers/Http/StaticController.js224
-rw-r--r--src/server/app/Controllers/Http/UserController.js228
-rw-r--r--src/server/app/Controllers/Http/WorkspaceController.js148
-rw-r--r--src/server/app/Exceptions/Handler.js45
-rw-r--r--src/server/app/Middleware/ConvertEmptyStringsToNull.js16
-rw-r--r--src/server/app/Models/Recipe.js8
-rw-r--r--src/server/app/Models/Service.js8
-rw-r--r--src/server/app/Models/Token.js8
-rw-r--r--src/server/app/Models/Traits/NoTimestamp.js15
-rw-r--r--src/server/app/Models/User.js8
-rw-r--r--src/server/app/Models/Workspace.js8
-rw-r--r--src/server/config/app.js242
-rw-r--r--src/server/config/auth.js93
-rw-r--r--src/server/config/bodyParser.js156
-rw-r--r--src/server/config/cors.js86
-rw-r--r--src/server/config/database.js83
-rw-r--r--src/server/config/drive.js45
-rw-r--r--src/server/config/hash.js48
-rw-r--r--src/server/config/session.js98
-rw-r--r--src/server/config/shield.js144
-rw-r--r--src/server/database/factory.js20
-rw-r--r--src/server/database/migrations/1566385379883_service_schema.js22
-rw-r--r--src/server/database/migrations/1566554231482_recipe_schema.js21
-rw-r--r--src/server/database/migrations/1566554359294_workspace_schema.js23
-rw-r--r--src/server/database/template.sqlitebin36864 -> 0 bytes
-rw-r--r--src/server/env.ini16
-rw-r--r--src/server/logo.pngbin298671 -> 0 bytes
-rw-r--r--src/server/package.json49
-rw-r--r--src/server/public/css/main.css69
-rw-r--r--src/server/public/css/vanilla.css138
-rw-r--r--src/server/resources/views/import.edge18
-rw-r--r--src/server/resources/views/index.edge32
-rw-r--r--src/server/resources/views/layouts/main.edge18
-rw-r--r--src/server/start.js41
-rw-r--r--src/server/start/app.js62
-rw-r--r--src/server/start/kernel.js56
-rw-r--r--src/server/start/routes.js74
45 files changed, 0 insertions, 2794 deletions
diff --git a/src/server/.editorconfig b/src/server/.editorconfig
deleted file mode 100644
index 914223976..000000000
--- a/src/server/.editorconfig
+++ /dev/null
@@ -1,13 +0,0 @@
1# editorconfig.org
2root = true
3
4[*]
5indent_size = 2
6indent_style = space
7end_of_line = lf
8charset = utf-8
9trim_trailing_whitespace = true
10insert_final_newline = true
11
12[*.md]
13trim_trailing_whitespace = false
diff --git a/src/server/.eslintrc.js b/src/server/.eslintrc.js
deleted file mode 100644
index d02f4890d..000000000
--- a/src/server/.eslintrc.js
+++ /dev/null
@@ -1,22 +0,0 @@
1module.exports = {
2 env: {
3 commonjs: true,
4 es6: true,
5 node: true,
6 },
7 extends: [
8 'airbnb-base',
9 ],
10 globals: {
11 Atomics: 'readonly',
12 SharedArrayBuffer: 'readonly',
13 use: 'readonly'
14 },
15 parserOptions: {
16 ecmaVersion: 2018,
17 },
18 rules: {
19 "class-methods-use-this": 'off',
20 "no-restricted-syntax": 'off',
21 },
22};
diff --git a/src/server/.gitattributes b/src/server/.gitattributes
deleted file mode 100644
index dfe077042..000000000
--- a/src/server/.gitattributes
+++ /dev/null
@@ -1,2 +0,0 @@
1# Auto detect text files and perform LF normalization
2* text=auto
diff --git a/src/server/.gitignore b/src/server/.gitignore
deleted file mode 100644
index d84ffadd4..000000000
--- a/src/server/.gitignore
+++ /dev/null
@@ -1,19 +0,0 @@
1# Node modules
2node_modules
3
4# Adonis directory for storing tmp files
5tmp
6
7# Environment variables, never commit this file
8.env
9
10# The development sqlite file
11database/development.sqlite
12database/adonis.sqlite
13
14# Uploaded recipes
15recipes/
16
17.DS_Store
18public/terms.html
19public/privacy.html
diff --git a/src/server/README.md b/src/server/README.md
deleted file mode 100644
index 0074f2314..000000000
--- a/src/server/README.md
+++ /dev/null
@@ -1,17 +0,0 @@
1<p align="center">
2 <img src="./logo.png" alt="" width="300"/>
3</p>
4
5# ferdi-internal-server
6Internal Ferdi Server used for storing settings without logging into an external server.
7
8## Configuration
9franz-server's configuration is saved inside the `env.ini` file. Besides AdonisJS's settings, ferdi-internal-server has the following custom settings:
10- `CONNECT_WITH_FRANZ` (`true` or `false`, default: `true`): Whether to enable connections to the Franz server. By enabling this option, ferdi-server can:
11 - Show the full Franz recipe library instead of only custom recipes
12 - Import Franz accounts
13
14## Importing your Franz account
15ferdi-server allows you to import your full Franz account, including all its settings.
16
17To import your Franz account, open `http://localhost:45569/import` in your browser and login using your Franz account details. ferdi-server will create a new user with the same credentials and copy your Franz settings, services and workspaces.
diff --git a/src/server/ace b/src/server/ace
deleted file mode 100644
index 42f8f10d1..000000000
--- a/src/server/ace
+++ /dev/null
@@ -1,21 +0,0 @@
1'use strict'
2
3/*
4|--------------------------------------------------------------------------
5| Ace Commands
6|--------------------------------------------------------------------------
7|
8| The ace file is just a regular Javascript file but with no extension. You
9| can call `node ace` followed by the command name and it just works.
10|
11| Also you can use `adonis` followed by the command name, since the adonis
12| global proxies all the ace commands.
13|
14*/
15
16const { Ignitor } = require('@adonisjs/ignitor')
17
18new Ignitor(require('@adonisjs/fold'))
19 .appRoot(__dirname)
20 .fireAce()
21 .catch(console.error)
diff --git a/src/server/app/Controllers/Http/RecipeController.js b/src/server/app/Controllers/Http/RecipeController.js
deleted file mode 100644
index 71ac12c0f..000000000
--- a/src/server/app/Controllers/Http/RecipeController.js
+++ /dev/null
@@ -1,119 +0,0 @@
1
2const Recipe = use('App/Models/Recipe');
3const Drive = use('Drive');
4const {
5 validateAll,
6} = use('Validator');
7const Env = use('Env');
8
9const fetch = require('node-fetch');
10
11class RecipeController {
12 // List official and custom recipes
13 async list({
14 response,
15 }) {
16 const officialRecipes = JSON.parse(await (await fetch('https://api.getferdi.com/v1/recipes')).text());
17 const customRecipesArray = (await Recipe.all()).rows;
18 const customRecipes = customRecipesArray.map(recipe => ({
19 id: recipe.recipeId,
20 name: recipe.name,
21 ...JSON.parse(recipe.data),
22 }));
23
24 const recipes = [
25 ...officialRecipes,
26 ...customRecipes,
27 ];
28
29 return response.send(recipes);
30 }
31
32 // Search official and custom recipes
33 async search({
34 request,
35 response,
36 }) {
37 // Validate user input
38 const validation = await validateAll(request.all(), {
39 needle: 'required',
40 });
41 if (validation.fails()) {
42 return response.status(401).send({
43 message: 'Please provide a needle',
44 messages: validation.messages(),
45 status: 401,
46 });
47 }
48
49 const needle = request.input('needle');
50
51 // Get results
52 let results;
53
54 if (needle === 'ferdi:custom') {
55 const dbResults = (await Recipe.all()).toJSON();
56 results = dbResults.map(recipe => ({
57 id: recipe.recipeId,
58 name: recipe.name,
59 ...JSON.parse(recipe.data),
60 }));
61 } else {
62 let remoteResults = [];
63 if (Env.get('CONNECT_WITH_FRANZ') == 'true') { // eslint-disable-line eqeqeq
64 remoteResults = JSON.parse(await (await fetch(`https://api.getferdi.com/v1/recipes/search?needle=${encodeURIComponent(needle)}`)).text());
65 }
66 const localResultsArray = (await Recipe.query().where('name', 'LIKE', `%${needle}%`).fetch()).toJSON();
67 const localResults = localResultsArray.map(recipe => ({
68 id: recipe.recipeId,
69 name: recipe.name,
70 ...JSON.parse(recipe.data),
71 }));
72
73 results = [
74 ...localResults,
75 ...remoteResults || [],
76 ];
77 }
78
79 return response.send(results);
80 }
81
82 // Download a recipe
83 async download({
84 response,
85 params,
86 }) {
87 // Validate user input
88 const validation = await validateAll(params, {
89 recipe: 'required|accepted',
90 });
91 if (validation.fails()) {
92 return response.status(401).send({
93 message: 'Please provide a recipe ID',
94 messages: validation.messages(),
95 status: 401,
96 });
97 }
98
99 const service = params.recipe;
100
101 // Check for invalid characters
102 if (/\.{1,}/.test(service) || /\/{1,}/.test(service)) {
103 return response.send('Invalid recipe name');
104 }
105
106 // Check if recipe exists in recipes folder
107 if (await Drive.exists(`${service}.tar.gz`)) {
108 return response.send(await Drive.get(`${service}.tar.gz`));
109 } if (Env.get('CONNECT_WITH_FRANZ') == 'true') { // eslint-disable-line eqeqeq
110 return response.redirect(`https://api.getferdi.com/v1/recipes/download/${service}`);
111 }
112 return response.status(400).send({
113 message: 'Recipe not found',
114 code: 'recipe-not-found',
115 });
116 }
117}
118
119module.exports = RecipeController;
diff --git a/src/server/app/Controllers/Http/ServiceController.js b/src/server/app/Controllers/Http/ServiceController.js
deleted file mode 100644
index ea7035ca1..000000000
--- a/src/server/app/Controllers/Http/ServiceController.js
+++ /dev/null
@@ -1,211 +0,0 @@
1const Service = use('App/Models/Service');
2const {
3 validateAll,
4} = use('Validator');
5
6const uuid = require('uuid/v4');
7
8class ServiceController {
9 // Create a new service for user
10 async create({
11 request,
12 response,
13 }) {
14 // Validate user input
15 const validation = await validateAll(request.all(), {
16 name: 'required|string',
17 recipeId: 'required',
18 });
19 if (validation.fails()) {
20 return response.status(401).send({
21 message: 'Invalid POST arguments',
22 messages: validation.messages(),
23 status: 401,
24 });
25 }
26
27 const data = request.all();
28
29 // Get new, unused uuid
30 let serviceId;
31 do {
32 serviceId = uuid();
33 } while ((await Service.query().where('serviceId', serviceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop
34
35 await Service.create({
36 serviceId,
37 name: data.name,
38 recipeId: data.recipeId,
39 settings: JSON.stringify(data),
40 });
41
42 return response.send({
43 data: {
44 userId: 1,
45 id: serviceId,
46 isEnabled: true,
47 isNotificationEnabled: true,
48 isBadgeEnabled: true,
49 isMuted: false,
50 isDarkModeEnabled: '',
51 spellcheckerLanguage: '',
52 order: 1,
53 customRecipe: false,
54 hasCustomIcon: false,
55 workspaces: [],
56 iconUrl: null,
57 ...data,
58 },
59 status: ['created'],
60 });
61 }
62
63 // List all services a user has created
64 async list({
65 response,
66 }) {
67 const services = (await Service.all()).rows;
68 // Convert to array with all data Franz wants
69 const servicesArray = services.map(service => ({
70 customRecipe: false,
71 hasCustomIcon: false,
72 isBadgeEnabled: true,
73 isDarkModeEnabled: '',
74 isEnabled: true,
75 isMuted: false,
76 isNotificationEnabled: true,
77 order: 1,
78 spellcheckerLanguage: '',
79 workspaces: [],
80 iconUrl: null,
81 ...JSON.parse(service.settings),
82 id: service.serviceId,
83 name: service.name,
84 recipeId: service.recipeId,
85 userId: 1,
86 }));
87
88 return response.send(servicesArray);
89 }
90
91 async edit({
92 request,
93 response,
94 params,
95 }) {
96 // Validate user input
97 const validation = await validateAll(request.all(), {
98 name: 'required',
99 });
100 if (validation.fails()) {
101 return response.status(401).send({
102 message: 'Invalid POST arguments',
103 messages: validation.messages(),
104 status: 401,
105 });
106 }
107
108 const data = request.all();
109 const {
110 id,
111 } = params;
112
113 // Get current settings from db
114 const serviceData = (await Service.query()
115 .where('serviceId', id).fetch()).rows[0];
116
117 const settings = {
118 ...JSON.parse(serviceData.settings),
119 ...data,
120 };
121
122 // Update data in database
123 await (Service.query()
124 .where('serviceId', id)).update({
125 name: data.name,
126 settings: JSON.stringify(settings),
127 });
128
129 // Get updated row
130 const service = (await Service.query()
131 .where('serviceId', id).fetch()).rows[0];
132
133 return response.send({
134 id: service.serviceId,
135 name: data.name,
136 ...settings,
137 userId: 1,
138 });
139 }
140
141 async reorder({
142 request,
143 response,
144 }) {
145 const data = request.all();
146
147 for (const service of Object.keys(data)) {
148 // Get current settings from db
149 const serviceData = (await Service.query() // eslint-disable-line no-await-in-loop
150 .where('serviceId', service).fetch()).rows[0];
151
152 const settings = {
153 ...JSON.parse(serviceData.settings),
154 order: data[service],
155 };
156
157 // Update data in database
158 await (Service.query() // eslint-disable-line no-await-in-loop
159 .where('serviceId', service))
160 .update({
161 settings: JSON.stringify(settings),
162 });
163 }
164
165 // Get new services
166 const services = (await Service.all()).rows;
167 // Convert to array with all data Franz wants
168 const servicesArray = services.map(service => ({
169 customRecipe: false,
170 hasCustomIcon: false,
171 isBadgeEnabled: true,
172 isDarkModeEnabled: '',
173 isEnabled: true,
174 isMuted: false,
175 isNotificationEnabled: true,
176 order: 1,
177 spellcheckerLanguage: '',
178 workspaces: [],
179 iconUrl: null,
180 ...JSON.parse(service.settings),
181 id: service.serviceId,
182 name: service.name,
183 recipeId: service.recipeId,
184 userId: 1,
185 }));
186
187 return response.send(servicesArray);
188 }
189
190 update({
191 response,
192 }) {
193 return response.send([]);
194 }
195
196 async delete({
197 params,
198 response,
199 }) {
200 // Update data in database
201 await (Service.query()
202 .where('serviceId', params.id)).delete();
203
204 return response.send({
205 message: 'Sucessfully deleted service',
206 status: 200,
207 });
208 }
209}
210
211module.exports = ServiceController;
diff --git a/src/server/app/Controllers/Http/StaticController.js b/src/server/app/Controllers/Http/StaticController.js
deleted file mode 100644
index b16e6cb6d..000000000
--- a/src/server/app/Controllers/Http/StaticController.js
+++ /dev/null
@@ -1,224 +0,0 @@
1
2/**
3 * Controller for routes with static responses
4 */
5
6class StaticController {
7 // Enable all features
8 features({
9 response,
10 }) {
11 return response.send({
12 needToWaitToProceed: false,
13 isSpellcheckerPremiumFeature: true,
14 isServiceProxyEnabled: true,
15 isServiceProxyPremiumFeature: true,
16 isWorkspacePremiumFeature: true,
17 isWorkspaceEnabled: true,
18 isAnnouncementsEnabled: true,
19 isSettingsWSEnabled: false,
20 isServiceLimitEnabled: false,
21 serviceLimitCount: 0,
22 isCommunityRecipesPremiumFeature: false,
23 });
24 }
25
26 // Return an empty array
27 emptyArray({
28 response,
29 }) {
30 return response.send([]);
31 }
32
33 // Payment plans availible
34 plans({
35 response,
36 }) {
37 return response.send({
38 month: {
39 id: 'franz-supporter-license',
40 price: 99,
41 },
42 year: {
43 id: 'franz-supporter-license-year-2019',
44 price: 99,
45 },
46 });
47 }
48
49 // Return list of popular recipes (copy of the response Franz's API is returning)
50 popularRecipes({
51 response,
52 }) {
53 return response.send([{
54 author: 'Stefan Malzner <stefan@adlk.io>',
55 featured: false,
56 id: 'slack',
57 name: 'Slack',
58 version: '1.0.4',
59 icons: {
60 png: 'https://cdn.franzinfra.com/recipes/dist/slack/src/icon.png',
61 svg: 'https://cdn.franzinfra.com/recipes/dist/slack/src/icon.svg',
62 },
63 }, {
64 author: 'Stefan Malzner <stefan@adlk.io>',
65 featured: false,
66 id: 'whatsapp',
67 name: 'WhatsApp',
68 version: '1.0.1',
69 icons: {
70 png: 'https://cdn.franzinfra.com/recipes/dist/whatsapp/src/icon.png',
71 svg: 'https://cdn.franzinfra.com/recipes/dist/whatsapp/src/icon.svg',
72 },
73 }, {
74 author: 'Stefan Malzner <stefan@adlk.io>',
75 featured: false,
76 id: 'messenger',
77 name: 'Messenger',
78 version: '1.0.6',
79 icons: {
80 png: 'https://cdn.franzinfra.com/recipes/dist/messenger/src/icon.png',
81 svg: 'https://cdn.franzinfra.com/recipes/dist/messenger/src/icon.svg',
82 },
83 }, {
84 author: 'Stefan Malzner <stefan@adlk.io>',
85 featured: false,
86 id: 'telegram',
87 name: 'Telegram',
88 version: '1.0.0',
89 icons: {
90 png: 'https://cdn.franzinfra.com/recipes/dist/telegram/src/icon.png',
91 svg: 'https://cdn.franzinfra.com/recipes/dist/telegram/src/icon.svg',
92 },
93 }, {
94 author: 'Stefan Malzner <stefan@adlk.io>',
95 featured: false,
96 id: 'gmail',
97 name: 'Gmail',
98 version: '1.0.0',
99 icons: {
100 png: 'https://cdn.franzinfra.com/recipes/dist/gmail/src/icon.png',
101 svg: 'https://cdn.franzinfra.com/recipes/dist/gmail/src/icon.svg',
102 },
103 }, {
104 author: 'Stefan Malzner <stefan@adlk.io>',
105 featured: false,
106 id: 'skype',
107 name: 'Skype',
108 version: '1.0.0',
109 icons: {
110 png: 'https://cdn.franzinfra.com/recipes/dist/skype/src/icon.png',
111 svg: 'https://cdn.franzinfra.com/recipes/dist/skype/src/icon.svg',
112 },
113 }, {
114 author: 'Stefan Malzner <stefan@adlk.io>',
115 featured: false,
116 id: 'hangouts',
117 name: 'Hangouts',
118 version: '1.0.0',
119 icons: {
120 png: 'https://cdn.franzinfra.com/recipes/dist/hangouts/src/icon.png',
121 svg: 'https://cdn.franzinfra.com/recipes/dist/hangouts/src/icon.svg',
122 },
123 }, {
124 author: 'Stefan Malzner <stefan@adlk.io>',
125 featured: false,
126 id: 'discord',
127 name: 'Discord',
128 version: '1.0.0',
129 icons: {
130 png: 'https://cdn.franzinfra.com/recipes/dist/discord/src/icon.png',
131 svg: 'https://cdn.franzinfra.com/recipes/dist/discord/src/icon.svg',
132 },
133 }, {
134 author: 'Stefan Malzner <stefan@adlk.io>',
135 featured: false,
136 id: 'tweetdeck',
137 name: 'Tweetdeck',
138 version: '1.0.1',
139 icons: {
140 png: 'https://cdn.franzinfra.com/recipes/dist/tweetdeck/src/icon.png',
141 svg: 'https://cdn.franzinfra.com/recipes/dist/tweetdeck/src/icon.svg',
142 },
143 }, {
144 author: 'Stefan Malzner <stefan@adlk.io>',
145 featured: false,
146 id: 'hipchat',
147 name: 'HipChat',
148 version: '1.0.1',
149 icons: {
150 png: 'https://cdn.franzinfra.com/recipes/dist/hipchat/src/icon.png',
151 svg: 'https://cdn.franzinfra.com/recipes/dist/hipchat/src/icon.svg',
152 },
153 }, {
154 author: 'Stefan Malzner <stefan@adlk.io>',
155 featured: false,
156 id: 'gmailinbox',
157 name: 'Inbox by Gmail',
158 version: '1.0.0',
159 icons: {
160 png: 'https://cdn.franzinfra.com/recipes/dist/gmailinbox/src/icon.png',
161 svg: 'https://cdn.franzinfra.com/recipes/dist/gmailinbox/src/icon.svg',
162 },
163 }, {
164 author: 'Stefan Malzner <stefan@adlk.io>',
165 featured: false,
166 id: 'rocketchat',
167 name: 'Rocket.Chat',
168 version: '1.0.1',
169 icons: {
170 png: 'https://cdn.franzinfra.com/recipes/dist/rocketchat/src/icon.png',
171 svg: 'https://cdn.franzinfra.com/recipes/dist/rocketchat/src/icon.svg',
172 },
173 }, {
174 author: 'Brian Gilbert <brian@briangilbert.net>',
175 featured: false,
176 id: 'gitter',
177 name: 'Gitter',
178 version: '1.0.0',
179 icons: {
180 png: 'https://cdn.franzinfra.com/recipes/dist/gitter/src/icon.png',
181 svg: 'https://cdn.franzinfra.com/recipes/dist/gitter/src/icon.svg',
182 },
183 }, {
184 author: 'Stefan Malzner <stefan@adlk.io>',
185 featured: false,
186 id: 'mattermost',
187 name: 'Mattermost',
188 version: '1.0.0',
189 icons: {
190 png: 'https://cdn.franzinfra.com/recipes/dist/mattermost/src/icon.png',
191 svg: 'https://cdn.franzinfra.com/recipes/dist/mattermost/src/icon.svg',
192 },
193 }, {
194 author: 'Franz <recipe@meetfranz.com>',
195 featured: false,
196 id: 'toggl',
197 name: 'toggl',
198 version: '1.0.0',
199 icons: {
200 png: 'https://cdn.franzinfra.com/recipes/dist/toggl/src/icon.png',
201 svg: 'https://cdn.franzinfra.com/recipes/dist/toggl/src/icon.svg',
202 },
203 }, {
204 author: 'Stuart Clark <stuart@realityloop.com>',
205 featured: false,
206 id: 'twist',
207 name: 'twist',
208 version: '1.0.0',
209 icons: {
210 png: 'https://cdn.franzinfra.com/recipes/dist/twist/src/icon.png',
211 svg: 'https://cdn.franzinfra.com/recipes/dist/twist/src/icon.svg',
212 },
213 }]);
214 }
215
216 // Show announcements
217 announcement({
218 response,
219 }) {
220 return response.send('No announcement found.');
221 }
222}
223
224module.exports = StaticController;
diff --git a/src/server/app/Controllers/Http/UserController.js b/src/server/app/Controllers/Http/UserController.js
deleted file mode 100644
index 07e118afd..000000000
--- a/src/server/app/Controllers/Http/UserController.js
+++ /dev/null
@@ -1,228 +0,0 @@
1const Service = use('App/Models/Service');
2const Workspace = use('App/Models/Workspace');
3const {
4 validateAll,
5} = use('Validator');
6
7const btoa = require('btoa');
8const fetch = require('node-fetch');
9const uuid = require('uuid/v4');
10const crypto = require('crypto');
11
12const apiRequest = (url, route, method, auth) => new Promise((resolve, reject) => {
13 const base = `${url}/v1/`;
14 const user = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Ferdi/5.3.0-beta.1 Chrome/69.0.3497.128 Electron/4.2.4 Safari/537.36';
15
16 try {
17 fetch(base + route, {
18 method,
19 headers: {
20 Authorization: `Bearer ${auth}`,
21 'User-Agent': user,
22 },
23 })
24 .then(data => data.json())
25 .then(json => resolve(json));
26 } catch (e) {
27 reject();
28 }
29});
30
31class UserController {
32 // Register a new user
33 async signup({
34 request,
35 response,
36 }) {
37 // Validate user input
38 const validation = await validateAll(request.all(), {
39 firstname: 'required',
40 email: 'required|email',
41 password: 'required',
42 });
43 if (validation.fails()) {
44 return response.status(401).send({
45 message: 'Invalid POST arguments',
46 messages: validation.messages(),
47 status: 401,
48 });
49 }
50
51 return response.send({
52 message: 'Successfully created account',
53 token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJGZXJkaSBJbnRlcm5hbCBTZXJ2ZXIiLCJpYXQiOjE1NzEwNDAyMTUsImV4cCI6MjUzMzk1NDE3ODQ0LCJhdWQiOiJnZXRmZXJkaS5jb20iLCJzdWIiOiJmZXJkaUBsb2NhbGhvc3QiLCJ1c2VySWQiOiIxIn0.9_TWFGp6HROv8Yg82Rt6i1-95jqWym40a-HmgrdMC6M',
54 });
55 }
56
57 // Login using an existing user
58 async login({
59 request,
60 response,
61 }) {
62 if (!request.header('Authorization')) {
63 return response.status(401).send({
64 message: 'Please provide authorization',
65 status: 401,
66 });
67 }
68
69 return response.send({
70 message: 'Successfully logged in',
71 token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJGZXJkaSBJbnRlcm5hbCBTZXJ2ZXIiLCJpYXQiOjE1NzEwNDAyMTUsImV4cCI6MjUzMzk1NDE3ODQ0LCJhdWQiOiJnZXRmZXJkaS5jb20iLCJzdWIiOiJmZXJkaUBsb2NhbGhvc3QiLCJ1c2VySWQiOiIxIn0.9_TWFGp6HROv8Yg82Rt6i1-95jqWym40a-HmgrdMC6M',
72 });
73 }
74
75 // Return information about the current user
76 async me({
77 response,
78 }) {
79 return response.send({
80 accountType: 'individual',
81 beta: false,
82 donor: {},
83 email: '',
84 emailValidated: true,
85 features: {},
86 firstname: 'Ferdi',
87 id: '82c1cf9d-ab58-4da2-b55e-aaa41d2142d8',
88 isPremium: true,
89 isSubscriptionOwner: true,
90 lastname: 'Application',
91 locale: 'en-US',
92 });
93 }
94
95
96 async import({
97 request,
98 response,
99 }) {
100 // Validate user input
101 const validation = await validateAll(request.all(), {
102 email: 'required|email',
103 password: 'required',
104 server: 'required',
105 });
106 if (validation.fails()) {
107 let errorMessage = 'There was an error while trying to import your account:\n';
108 for (const message of validation.messages()) {
109 if (message.validation === 'required') {
110 errorMessage += `- Please make sure to supply your ${message.field}\n`;
111 } else if (message.validation === 'unique') {
112 errorMessage += '- There is already a user with this email.\n';
113 } else {
114 errorMessage += `${message.message}\n`;
115 }
116 }
117 return response.status(401).send(errorMessage);
118 }
119
120 const {
121 email,
122 password,
123 server,
124 } = request.all();
125
126 const hashedPassword = crypto.createHash('sha256').update(password).digest('base64');
127
128 const base = `${server}/v1/`;
129 const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Ferdi/5.3.0-beta.1 Chrome/69.0.3497.128 Electron/4.2.4 Safari/537.36';
130
131 // Try to get an authentication token
132 let token;
133 try {
134 const basicToken = btoa(`${email}:${hashedPassword}`);
135
136 const rawResponse = await fetch(`${base}auth/login`, {
137 method: 'POST',
138 headers: {
139 Authorization: `Basic ${basicToken}`,
140 'User-Agent': userAgent,
141 },
142 });
143 const content = await rawResponse.json();
144
145 if (!content.message || content.message !== 'Successfully logged in') {
146 const errorMessage = 'Could not login into Franz with your supplied credentials. Please check and try again';
147 return response.status(401).send(errorMessage);
148 }
149
150 // eslint-disable-next-line prefer-destructuring
151 token = content.token;
152 } catch (e) {
153 return response.status(401).send({
154 message: 'Cannot login to Franz',
155 error: e,
156 });
157 }
158
159 // Get user information
160 let userInf = false;
161 try {
162 userInf = await apiRequest(server, 'me', 'GET', token);
163 } catch (e) {
164 const errorMessage = `Could not get your user info from Franz. Please check your credentials or try again later.\nError: ${e}`;
165 return response.status(401).send(errorMessage);
166 }
167 if (!userInf) {
168 const errorMessage = 'Could not get your user info from Franz. Please check your credentials or try again later';
169 return response.status(401).send(errorMessage);
170 }
171
172 const serviceIdTranslation = {};
173
174 // Import services
175 try {
176 const services = await apiRequest(server, 'me/services', 'GET', token);
177
178 for (const service of services) {
179 // Get new, unused uuid
180 let serviceId;
181 do {
182 serviceId = uuid();
183 } while ((await Service.query().where('serviceId', serviceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop
184
185 await Service.create({ // eslint-disable-line no-await-in-loop
186 serviceId,
187 name: service.name,
188 recipeId: service.recipeId,
189 settings: JSON.stringify(service),
190 });
191
192 serviceIdTranslation[service.id] = serviceId;
193 }
194 } catch (e) {
195 const errorMessage = `Could not import your services into our system.\nError: ${e}`;
196 return response.status(401).send(errorMessage);
197 }
198
199 // Import workspaces
200 try {
201 const workspaces = await apiRequest(server, 'workspace', 'GET', token);
202
203 for (const workspace of workspaces) {
204 let workspaceId;
205 do {
206 workspaceId = uuid();
207 } while ((await Workspace.query().where('workspaceId', workspaceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop
208
209 const services = workspace.services.map(service => serviceIdTranslation[service]);
210
211 await Workspace.create({ // eslint-disable-line no-await-in-loop
212 workspaceId,
213 name: workspace.name,
214 order: workspace.order,
215 services: JSON.stringify(services),
216 data: JSON.stringify({}),
217 });
218 }
219 } catch (e) {
220 const errorMessage = `Could not import your workspaces into our system.\nError: ${e}`;
221 return response.status(401).send(errorMessage);
222 }
223
224 return response.send('Your account has been imported. You can now use your Franz account in Ferdi.');
225 }
226}
227
228module.exports = UserController;
diff --git a/src/server/app/Controllers/Http/WorkspaceController.js b/src/server/app/Controllers/Http/WorkspaceController.js
deleted file mode 100644
index 7990b8434..000000000
--- a/src/server/app/Controllers/Http/WorkspaceController.js
+++ /dev/null
@@ -1,148 +0,0 @@
1const Workspace = use('App/Models/Workspace');
2const {
3 validateAll,
4} = use('Validator');
5
6const uuid = require('uuid/v4');
7
8class WorkspaceController {
9 // Create a new workspace for user
10 async create({
11 request,
12 response,
13 }) {
14 // Validate user input
15 const validation = await validateAll(request.all(), {
16 name: 'required|alpha',
17 });
18 if (validation.fails()) {
19 return response.status(401).send({
20 message: 'Invalid POST arguments',
21 messages: validation.messages(),
22 status: 401,
23 });
24 }
25
26 const data = request.all();
27
28 // Get new, unused uuid
29 let workspaceId;
30 do {
31 workspaceId = uuid();
32 } while ((await Workspace.query().where('workspaceId', workspaceId).fetch()).rows.length > 0); // eslint-disable-line no-await-in-loop
33
34 const order = (await Workspace.all()).rows.length;
35
36 await Workspace.create({
37 workspaceId,
38 name: data.name,
39 order,
40 services: JSON.stringify([]),
41 data: JSON.stringify(data),
42 });
43
44 return response.send({
45 userId: 1,
46 name: data.name,
47 id: workspaceId,
48 order,
49 workspaces: [],
50 });
51 }
52
53 async edit({
54 request,
55 response,
56 params,
57 }) {
58 // Validate user input
59 const validation = await validateAll(request.all(), {
60 name: 'required|alpha',
61 services: 'required|array',
62 });
63 if (validation.fails()) {
64 return response.status(401).send({
65 message: 'Invalid POST arguments',
66 messages: validation.messages(),
67 status: 401,
68 });
69 }
70
71 const data = request.all();
72 const {
73 id,
74 } = params;
75
76 // Update data in database
77 await (Workspace.query()
78 .where('workspaceId', id)).update({
79 name: data.name,
80 services: JSON.stringify(data.services),
81 });
82
83 // Get updated row
84 const workspace = (await Workspace.query()
85 .where('workspaceId', id).fetch()).rows[0];
86
87 return response.send({
88 id: workspace.workspaceId,
89 name: data.name,
90 order: workspace.order,
91 services: data.services,
92 userId: 1,
93 });
94 }
95
96 async delete({
97 request,
98 response,
99 params,
100 }) {
101 // Validate user input
102 const validation = await validateAll(request.all(), {
103 id: 'required',
104 });
105 if (validation.fails()) {
106 return response.status(401).send({
107 message: 'Invalid POST arguments',
108 messages: validation.messages(),
109 status: 401,
110 });
111 }
112
113 const {
114 id,
115 } = params;
116
117 // Update data in database
118 await (Workspace.query()
119 .where('workspaceId', id)).delete();
120
121 return response.send({
122 message: 'Successfully deleted workspace',
123 });
124 }
125
126 // List all workspaces a user has created
127 async list({
128 response,
129 }) {
130 const workspaces = (await Workspace.all()).rows;
131 // Convert to array with all data Franz wants
132 let workspacesArray = [];
133 if (workspaces) {
134 workspacesArray = workspaces.map(workspace => ({
135 id: workspace.workspaceId,
136 name: workspace.name,
137 order: workspace.order,
138 services: JSON.parse(workspace.services),
139 userId: 1,
140 }));
141 }
142
143
144 return response.send(workspacesArray);
145 }
146}
147
148module.exports = WorkspaceController;
diff --git a/src/server/app/Exceptions/Handler.js b/src/server/app/Exceptions/Handler.js
deleted file mode 100644
index e8d2d2ee2..000000000
--- a/src/server/app/Exceptions/Handler.js
+++ /dev/null
@@ -1,45 +0,0 @@
1
2const BaseExceptionHandler = use('BaseExceptionHandler');
3
4/**
5 * This class handles all exceptions thrown during
6 * the HTTP request lifecycle.
7 *
8 * @class ExceptionHandler
9 */
10class ExceptionHandler extends BaseExceptionHandler {
11 /**
12 * Handle exception thrown during the HTTP lifecycle
13 *
14 * @method handle
15 *
16 * @param {Object} error
17 * @param {Object} options.request
18 * @param {Object} options.response
19 *
20 * @return {void}
21 */
22 async handle(error, { response }) {
23 if (error.name === 'ValidationException') {
24 return response.status(400).send('Invalid arguments');
25 }
26
27 return response.status(error.status).send(error.message);
28 }
29
30 /**
31 * Report exception for logging or debugging.
32 *
33 * @method report
34 *
35 * @param {Object} error
36 * @param {Object} options.request
37 *
38 * @return {void}
39 */
40 async report() {
41 return true;
42 }
43}
44
45module.exports = ExceptionHandler;
diff --git a/src/server/app/Middleware/ConvertEmptyStringsToNull.js b/src/server/app/Middleware/ConvertEmptyStringsToNull.js
deleted file mode 100644
index bc3079a7f..000000000
--- a/src/server/app/Middleware/ConvertEmptyStringsToNull.js
+++ /dev/null
@@ -1,16 +0,0 @@
1
2class ConvertEmptyStringsToNull {
3 async handle({ request }, next) {
4 if (Object.keys(request.body).length) {
5 request.body = Object.assign(
6 ...Object.keys(request.body).map(key => ({
7 [key]: request.body[key] !== '' ? request.body[key] : null,
8 })),
9 );
10 }
11
12 await next();
13 }
14}
15
16module.exports = ConvertEmptyStringsToNull;
diff --git a/src/server/app/Models/Recipe.js b/src/server/app/Models/Recipe.js
deleted file mode 100644
index da3618bf7..000000000
--- a/src/server/app/Models/Recipe.js
+++ /dev/null
@@ -1,8 +0,0 @@
1
2/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
3const Model = use('Model');
4
5class Recipe extends Model {
6}
7
8module.exports = Recipe;
diff --git a/src/server/app/Models/Service.js b/src/server/app/Models/Service.js
deleted file mode 100644
index 20679feb1..000000000
--- a/src/server/app/Models/Service.js
+++ /dev/null
@@ -1,8 +0,0 @@
1
2/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
3const Model = use('Model');
4
5class Service extends Model {
6}
7
8module.exports = Service;
diff --git a/src/server/app/Models/Token.js b/src/server/app/Models/Token.js
deleted file mode 100644
index f6bec0852..000000000
--- a/src/server/app/Models/Token.js
+++ /dev/null
@@ -1,8 +0,0 @@
1
2/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
3const Model = use('Model');
4
5class Token extends Model {
6}
7
8module.exports = Token;
diff --git a/src/server/app/Models/Traits/NoTimestamp.js b/src/server/app/Models/Traits/NoTimestamp.js
deleted file mode 100644
index c647428b3..000000000
--- a/src/server/app/Models/Traits/NoTimestamp.js
+++ /dev/null
@@ -1,15 +0,0 @@
1
2class NoTimestamp {
3 register(Model) {
4 Object.defineProperties(Model, {
5 createdAtColumn: {
6 get: () => null,
7 },
8 updatedAtColumn: {
9 get: () => null,
10 },
11 });
12 }
13}
14
15module.exports = NoTimestamp;
diff --git a/src/server/app/Models/User.js b/src/server/app/Models/User.js
deleted file mode 100644
index 907710d8d..000000000
--- a/src/server/app/Models/User.js
+++ /dev/null
@@ -1,8 +0,0 @@
1// File is required by AdonisJS but not used by the server
2/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
3const Model = use('Model');
4
5class User extends Model {
6}
7
8module.exports = User;
diff --git a/src/server/app/Models/Workspace.js b/src/server/app/Models/Workspace.js
deleted file mode 100644
index 3b73cbf33..000000000
--- a/src/server/app/Models/Workspace.js
+++ /dev/null
@@ -1,8 +0,0 @@
1
2/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
3const Model = use('Model');
4
5class Workspace extends Model {
6}
7
8module.exports = Workspace;
diff --git a/src/server/config/app.js b/src/server/config/app.js
deleted file mode 100644
index 7938b81df..000000000
--- a/src/server/config/app.js
+++ /dev/null
@@ -1,242 +0,0 @@
1
2/** @type {import('@adonisjs/framework/src/Env')} */
3const Env = use('Env');
4
5module.exports = {
6
7 /*
8 |--------------------------------------------------------------------------
9 | Application Name
10 |--------------------------------------------------------------------------
11 |
12 | This value is the name of your application and can used when you
13 | need to place the application's name in a email, view or
14 | other location.
15 |
16 */
17
18 name: Env.get('APP_NAME', 'Ferdi Internal Server'),
19
20 /*
21 |--------------------------------------------------------------------------
22 | App Key
23 |--------------------------------------------------------------------------
24 |
25 | App key is a randomly generated 16 or 32 characters long string required
26 | to encrypt cookies, sessions and other sensitive data.
27 |
28 */
29 appKey: Env.getOrFail('APP_KEY'),
30
31 http: {
32 /*
33 |--------------------------------------------------------------------------
34 | Allow Method Spoofing
35 |--------------------------------------------------------------------------
36 |
37 | Method spoofing allows to make requests by spoofing the http verb.
38 | Which means you can make a GET request but instruct the server to
39 | treat as a POST or PUT request. If you want this feature, set the
40 | below value to true.
41 |
42 */
43 allowMethodSpoofing: true,
44
45 /*
46 |--------------------------------------------------------------------------
47 | Trust Proxy
48 |--------------------------------------------------------------------------
49 |
50 | Trust proxy defines whether X-Forwarded-* headers should be trusted or not.
51 | When your application is behind a proxy server like nginx, these values
52 | are set automatically and should be trusted. Apart from setting it
53 | to true or false Adonis supports handful or ways to allow proxy
54 | values. Read documentation for that.
55 |
56 */
57 trustProxy: false,
58
59 /*
60 |--------------------------------------------------------------------------
61 | Subdomains
62 |--------------------------------------------------------------------------
63 |
64 | Offset to be used for returning subdomains for a given request.For
65 | majority of applications it will be 2, until you have nested
66 | sudomains.
67 | cheatsheet.adonisjs.com - offset - 2
68 | virk.cheatsheet.adonisjs.com - offset - 3
69 |
70 */
71 subdomainOffset: 2,
72
73 /*
74 |--------------------------------------------------------------------------
75 | JSONP Callback
76 |--------------------------------------------------------------------------
77 |
78 | Default jsonp callback to be used when callback query string is missing
79 | in request url.
80 |
81 */
82 jsonpCallback: 'callback',
83
84
85 /*
86 |--------------------------------------------------------------------------
87 | Etag
88 |--------------------------------------------------------------------------
89 |
90 | Set etag on all HTTP response. In order to disable for selected routes,
91 | you can call the `response.send` with an options object as follows.
92 |
93 | response.send('Hello', { ignoreEtag: true })
94 |
95 */
96 etag: false,
97 },
98
99 views: {
100 /*
101 |--------------------------------------------------------------------------
102 | Cache Views
103 |--------------------------------------------------------------------------
104 |
105 | Define whether or not to cache the compiled view. Set it to true in
106 | production to optimize view loading time.
107 |
108 */
109 cache: Env.get('CACHE_VIEWS', true),
110 },
111
112 static: {
113 /*
114 |--------------------------------------------------------------------------
115 | Dot Files
116 |--------------------------------------------------------------------------
117 |
118 | Define how to treat dot files when trying to server static resources.
119 | By default it is set to ignore, which will pretend that dotfiles
120 | does not exists.
121 |
122 | Can be one of the following
123 | ignore, deny, allow
124 |
125 */
126 dotfiles: 'ignore',
127
128 /*
129 |--------------------------------------------------------------------------
130 | ETag
131 |--------------------------------------------------------------------------
132 |
133 | Enable or disable etag generation
134 |
135 */
136 etag: true,
137
138 /*
139 |--------------------------------------------------------------------------
140 | Extensions
141 |--------------------------------------------------------------------------
142 |
143 | Set file extension fallbacks. When set, if a file is not found, the given
144 | extensions will be added to the file name and search for. The first
145 | that exists will be served. Example: ['html', 'htm'].
146 |
147 */
148 extensions: false,
149 },
150
151 locales: {
152 /*
153 |--------------------------------------------------------------------------
154 | Loader
155 |--------------------------------------------------------------------------
156 |
157 | The loader to be used for fetching and updating locales. Below is the
158 | list of available options.
159 |
160 | file, database
161 |
162 */
163 loader: 'file',
164
165 /*
166 |--------------------------------------------------------------------------
167 | Default Locale
168 |--------------------------------------------------------------------------
169 |
170 | Default locale to be used by Antl provider. You can always switch drivers
171 | in runtime or use the official Antl middleware to detect the driver
172 | based on HTTP headers/query string.
173 |
174 */
175 locale: 'en',
176 },
177
178 logger: {
179 /*
180 |--------------------------------------------------------------------------
181 | Transport
182 |--------------------------------------------------------------------------
183 |
184 | Transport to be used for logging messages. You can have multiple
185 | transports using same driver.
186 |
187 | Available drivers are: `file` and `console`.
188 |
189 */
190 transport: 'console',
191
192 /*
193 |--------------------------------------------------------------------------
194 | Console Transport
195 |--------------------------------------------------------------------------
196 |
197 | Using `console` driver for logging. This driver writes to `stdout`
198 | and `stderr`
199 |
200 */
201 console: {
202 driver: 'console',
203 name: 'adonis-app',
204 level: 'info',
205 },
206
207 /*
208 |--------------------------------------------------------------------------
209 | File Transport
210 |--------------------------------------------------------------------------
211 |
212 | File transport uses file driver and writes log messages for a given
213 | file inside `tmp` directory for your app.
214 |
215 | For a different directory, set an absolute path for the filename.
216 |
217 */
218 file: {
219 driver: 'file',
220 name: 'adonis-app',
221 filename: 'adonis.log',
222 level: 'info',
223 },
224 },
225
226 /*
227 |--------------------------------------------------------------------------
228 | Generic Cookie Options
229 |--------------------------------------------------------------------------
230 |
231 | The following cookie options are generic settings used by AdonisJs to create
232 | cookies. However, some parts of the application like `sessions` can have
233 | separate settings for cookies inside `config/session.js`.
234 |
235 */
236 cookie: {
237 httpOnly: true,
238 sameSite: false,
239 path: '/',
240 maxAge: 7200,
241 },
242};
diff --git a/src/server/config/auth.js b/src/server/config/auth.js
deleted file mode 100644
index b831b06c6..000000000
--- a/src/server/config/auth.js
+++ /dev/null
@@ -1,93 +0,0 @@
1
2/** @type {import('@adonisjs/framework/src/Env')} */
3const Env = use('Env');
4
5module.exports = {
6 /*
7 |--------------------------------------------------------------------------
8 | Authenticator
9 |--------------------------------------------------------------------------
10 |
11 | Authentication is a combination of serializer and scheme with extra
12 | config to define on how to authenticate a user.
13 |
14 | Available Schemes - basic, session, jwt, api
15 | Available Serializers - lucid, database
16 |
17 */
18 authenticator: 'jwt',
19
20 /*
21 |--------------------------------------------------------------------------
22 | Session
23 |--------------------------------------------------------------------------
24 |
25 | Session authenticator makes use of sessions to authenticate a user.
26 | Session authentication is always persistent.
27 |
28 */
29 session: {
30 serializer: 'lucid',
31 model: 'App/Models/User',
32 scheme: 'session',
33 uid: 'email',
34 password: 'password',
35 },
36
37 /*
38 |--------------------------------------------------------------------------
39 | Basic Auth
40 |--------------------------------------------------------------------------
41 |
42 | The basic auth authenticator uses basic auth header to authenticate a
43 | user.
44 |
45 | NOTE:
46 | This scheme is not persistent and users are supposed to pass
47 | login credentials on each request.
48 |
49 */
50 basic: {
51 serializer: 'lucid',
52 model: 'App/Models/User',
53 scheme: 'basic',
54 uid: 'email',
55 password: 'password',
56 },
57
58 /*
59 |--------------------------------------------------------------------------
60 | Jwt
61 |--------------------------------------------------------------------------
62 |
63 | The jwt authenticator works by passing a jwt token on each HTTP request
64 | via HTTP `Authorization` header.
65 |
66 */
67 jwt: {
68 serializer: 'lucid',
69 model: 'App/Models/User',
70 scheme: 'jwt',
71 uid: 'email',
72 password: 'password',
73 options: {
74 secret: Env.get('APP_KEY'),
75 },
76 },
77
78 /*
79 |--------------------------------------------------------------------------
80 | Api
81 |--------------------------------------------------------------------------
82 |
83 | The Api scheme makes use of API personal tokens to authenticate a user.
84 |
85 */
86 api: {
87 serializer: 'lucid',
88 model: 'App/Models/User',
89 scheme: 'api',
90 uid: 'email',
91 password: 'password',
92 },
93};
diff --git a/src/server/config/bodyParser.js b/src/server/config/bodyParser.js
deleted file mode 100644
index c336e67d2..000000000
--- a/src/server/config/bodyParser.js
+++ /dev/null
@@ -1,156 +0,0 @@
1
2module.exports = {
3 /*
4 |--------------------------------------------------------------------------
5 | JSON Parser
6 |--------------------------------------------------------------------------
7 |
8 | Below settings are applied when the request body contains a JSON payload.
9 | If you want body parser to ignore JSON payloads, then simply set `types`
10 | to an empty array.
11 */
12 json: {
13 /*
14 |--------------------------------------------------------------------------
15 | limit
16 |--------------------------------------------------------------------------
17 |
18 | Defines the limit of JSON that can be sent by the client. If payload
19 | is over 1mb it will not be processed.
20 |
21 */
22 limit: '50mb',
23
24 /*
25 |--------------------------------------------------------------------------
26 | strict
27 |--------------------------------------------------------------------------
28 |
29 | When `strict` is set to true, body parser will only parse Arrays and
30 | Object. Otherwise everything parseable by `JSON.parse` is parsed.
31 |
32 */
33 strict: true,
34
35 /*
36 |--------------------------------------------------------------------------
37 | types
38 |--------------------------------------------------------------------------
39 |
40 | Which content types are processed as JSON payloads. You are free to
41 | add your own types here, but the request body should be parseable
42 | by `JSON.parse` method.
43 |
44 */
45 types: [
46 'application/json',
47 'application/json-patch+json',
48 'application/vnd.api+json',
49 'application/csp-report',
50 ],
51 },
52
53 /*
54 |--------------------------------------------------------------------------
55 | Raw Parser
56 |--------------------------------------------------------------------------
57 |
58 |
59 |
60 */
61 raw: {
62 types: [
63 'text/*',
64 ],
65 },
66
67 /*
68 |--------------------------------------------------------------------------
69 | Form Parser
70 |--------------------------------------------------------------------------
71 |
72 |
73 |
74 */
75 form: {
76 types: [
77 'application/x-www-form-urlencoded',
78 ],
79 },
80
81 /*
82 |--------------------------------------------------------------------------
83 | Files Parser
84 |--------------------------------------------------------------------------
85 |
86 |
87 |
88 */
89 files: {
90 types: [
91 'multipart/form-data',
92 ],
93
94 /*
95 |--------------------------------------------------------------------------
96 | Max Size
97 |--------------------------------------------------------------------------
98 |
99 | Below value is the max size of all the files uploaded to the server. It
100 | is validated even before files have been processed and hard exception
101 | is thrown.
102 |
103 | Consider setting a reasonable value here, otherwise people may upload GB's
104 | of files which will keep your server busy.
105 |
106 | Also this value is considered when `autoProcess` is set to true.
107 |
108 */
109 maxSize: '20mb',
110
111 /*
112 |--------------------------------------------------------------------------
113 | Auto Process
114 |--------------------------------------------------------------------------
115 |
116 | Whether or not to auto-process files. Since HTTP servers handle files via
117 | couple of specific endpoints. It is better to set this value off and
118 | manually process the files when required.
119 |
120 | This value can contain a boolean or an array of route patterns
121 | to be autoprocessed.
122 */
123 autoProcess: true,
124
125 /*
126 |--------------------------------------------------------------------------
127 | Process Manually
128 |--------------------------------------------------------------------------
129 |
130 | The list of routes that should not process files and instead rely on
131 | manual process. This list should only contain routes when autoProcess
132 | is to true. Otherwise everything is processed manually.
133 |
134 */
135 processManually: [],
136
137 /*
138 |--------------------------------------------------------------------------
139 | Temporary file name
140 |--------------------------------------------------------------------------
141 |
142 | Define a function, which should return a string to be used as the
143 | tmp file name.
144 |
145 | If not defined, Bodyparser will use `uuid` as the tmp file name.
146 |
147 | To be defined as. If you are defining the function, then do make sure
148 | to return a value from it.
149 |
150 | tmpFileName () {
151 | return 'some-unique-value'
152 | }
153 |
154 */
155 },
156};
diff --git a/src/server/config/cors.js b/src/server/config/cors.js
deleted file mode 100644
index 7ebbe3ffa..000000000
--- a/src/server/config/cors.js
+++ /dev/null
@@ -1,86 +0,0 @@
1
2module.exports = {
3 /*
4 |--------------------------------------------------------------------------
5 | Origin
6 |--------------------------------------------------------------------------
7 |
8 | Set a list of origins to be allowed. The value can be one of the following
9 |
10 | Boolean: true - Allow current request origin
11 | Boolean: false - Disallow all
12 | String - Comma separated list of allowed origins
13 | Array - An array of allowed origins
14 | String: * - A wildcard to allow current request origin
15 | Function - Receives the current origin and should return one of the above values.
16 |
17 */
18 origin: false,
19
20 /*
21 |--------------------------------------------------------------------------
22 | Methods
23 |--------------------------------------------------------------------------
24 |
25 | HTTP methods to be allowed. The value can be one of the following
26 |
27 | String - Comma separated list of allowed methods
28 | Array - An array of allowed methods
29 |
30 */
31 methods: ['GET', 'PUT', 'PATCH', 'POST', 'DELETE'],
32
33 /*
34 |--------------------------------------------------------------------------
35 | Headers
36 |--------------------------------------------------------------------------
37 |
38 | List of headers to be allowed via Access-Control-Request-Headers header.
39 | The value can be one of the following.
40 |
41 | Boolean: true - Allow current request headers
42 | Boolean: false - Disallow all
43 | String - Comma separated list of allowed headers
44 | Array - An array of allowed headers
45 | String: * - A wildcard to allow current request headers
46 | Function - Receives the current header and should return one of the above values.
47 |
48 */
49 headers: true,
50
51 /*
52 |--------------------------------------------------------------------------
53 | Expose Headers
54 |--------------------------------------------------------------------------
55 |
56 | A list of headers to be exposed via `Access-Control-Expose-Headers`
57 | header. The value can be one of the following.
58 |
59 | Boolean: false - Disallow all
60 | String: Comma separated list of allowed headers
61 | Array - An array of allowed headers
62 |
63 */
64 exposeHeaders: false,
65
66 /*
67 |--------------------------------------------------------------------------
68 | Credentials
69 |--------------------------------------------------------------------------
70 |
71 | Define Access-Control-Allow-Credentials header. It should always be a
72 | boolean.
73 |
74 */
75 credentials: false,
76
77 /*
78 |--------------------------------------------------------------------------
79 | MaxAge
80 |--------------------------------------------------------------------------
81 |
82 | Define Access-Control-Allow-Max-Age
83 |
84 */
85 maxAge: 90,
86};
diff --git a/src/server/config/database.js b/src/server/config/database.js
deleted file mode 100644
index a413f7050..000000000
--- a/src/server/config/database.js
+++ /dev/null
@@ -1,83 +0,0 @@
1
2/** @type {import('@adonisjs/framework/src/Env')} */
3const Env = use('Env');
4
5const dbPath = process.env.DB_PATH;
6
7module.exports = {
8 /*
9 |--------------------------------------------------------------------------
10 | Default Connection
11 |--------------------------------------------------------------------------
12 |
13 | Connection defines the default connection settings to be used while
14 | interacting with SQL databases.
15 |
16 */
17 connection: Env.get('DB_CONNECTION', 'sqlite'),
18
19 /*
20 |--------------------------------------------------------------------------
21 | Sqlite
22 |--------------------------------------------------------------------------
23 |
24 | Sqlite is a flat file database and can be a good choice for a development
25 | environment.
26 |
27 | npm i --save sqlite3
28 |
29 */
30 sqlite: {
31 client: 'sqlite3',
32 connection: {
33 // filename: Helpers.databasePath(`${Env.get('DB_DATABASE', 'development')}.sqlite`),
34 filename: dbPath,
35 },
36 useNullAsDefault: true,
37 debug: Env.get('DB_DEBUG', false),
38 },
39
40 /*
41 |--------------------------------------------------------------------------
42 | MySQL
43 |--------------------------------------------------------------------------
44 |
45 | Here we define connection settings for MySQL database.
46 |
47 | npm i --save mysql
48 |
49 */
50 mysql: {
51 client: 'mysql',
52 connection: {
53 host: Env.get('DB_HOST', 'localhost'),
54 port: Env.get('DB_PORT', ''),
55 user: Env.get('DB_USER', 'root'),
56 password: Env.get('DB_PASSWORD', ''),
57 database: Env.get('DB_DATABASE', 'adonis'),
58 },
59 debug: Env.get('DB_DEBUG', false),
60 },
61
62 /*
63 |--------------------------------------------------------------------------
64 | PostgreSQL
65 |--------------------------------------------------------------------------
66 |
67 | Here we define connection settings for PostgreSQL database.
68 |
69 | npm i --save pg
70 |
71 */
72 pg: {
73 client: 'pg',
74 connection: {
75 host: Env.get('DB_HOST', 'localhost'),
76 port: Env.get('DB_PORT', ''),
77 user: Env.get('DB_USER', 'root'),
78 password: Env.get('DB_PASSWORD', ''),
79 database: Env.get('DB_DATABASE', 'adonis'),
80 },
81 debug: Env.get('DB_DEBUG', false),
82 },
83};
diff --git a/src/server/config/drive.js b/src/server/config/drive.js
deleted file mode 100644
index 617ce470a..000000000
--- a/src/server/config/drive.js
+++ /dev/null
@@ -1,45 +0,0 @@
1const Env = use('Env');
2
3module.exports = {
4 /*
5 |--------------------------------------------------------------------------
6 | Default disk
7 |--------------------------------------------------------------------------
8 |
9 | The default disk is used when you interact with the file system without
10 | defining a disk name
11 |
12 */
13 default: 'local',
14
15 disks: {
16 /*
17 |--------------------------------------------------------------------------
18 | Local
19 |--------------------------------------------------------------------------
20 |
21 | Local disk interacts with the a local folder inside your application
22 |
23 */
24 local: {
25 root: `${__dirname}/../recipes`,
26 driver: 'local',
27 },
28
29 /*
30 |--------------------------------------------------------------------------
31 | S3
32 |--------------------------------------------------------------------------
33 |
34 | S3 disk interacts with a bucket on aws s3
35 |
36 */
37 s3: {
38 driver: 's3',
39 key: Env.get('S3_KEY'),
40 secret: Env.get('S3_SECRET'),
41 bucket: Env.get('S3_BUCKET'),
42 region: Env.get('S3_REGION'),
43 },
44 },
45};
diff --git a/src/server/config/hash.js b/src/server/config/hash.js
deleted file mode 100644
index 297c977fc..000000000
--- a/src/server/config/hash.js
+++ /dev/null
@@ -1,48 +0,0 @@
1
2/** @type {import('@adonisjs/framework/src/Env')} */
3const Env = use('Env');
4
5module.exports = {
6 /*
7 |--------------------------------------------------------------------------
8 | Driver
9 |--------------------------------------------------------------------------
10 |
11 | Driver to be used for hashing values. The same driver is used by the
12 | auth module too.
13 |
14 */
15 driver: Env.get('HASH_DRIVER', 'bcrypt'),
16
17 /*
18 |--------------------------------------------------------------------------
19 | Bcrypt
20 |--------------------------------------------------------------------------
21 |
22 | Config related to bcrypt hashing. https://www.npmjs.com/package/bcrypt
23 | package is used internally.
24 |
25 */
26 bcrypt: {
27 rounds: 10,
28 },
29
30 /*
31 |--------------------------------------------------------------------------
32 | Argon
33 |--------------------------------------------------------------------------
34 |
35 | Config related to argon. https://www.npmjs.com/package/argon2 package is
36 | used internally.
37 |
38 | Since argon is optional, you will have to install the dependency yourself
39 |
40 |============================================================================
41 | npm i argon2
42 |============================================================================
43 |
44 */
45 argon: {
46 type: 1,
47 },
48};
diff --git a/src/server/config/session.js b/src/server/config/session.js
deleted file mode 100644
index bce28bdd9..000000000
--- a/src/server/config/session.js
+++ /dev/null
@@ -1,98 +0,0 @@
1
2const Env = use('Env');
3
4module.exports = {
5 /*
6 |--------------------------------------------------------------------------
7 | Session Driver
8 |--------------------------------------------------------------------------
9 |
10 | The session driver to be used for storing session values. It can be
11 | cookie, file or redis.
12 |
13 | For `redis` driver, make sure to install and register `@adonisjs/redis`
14 |
15 */
16 driver: Env.get('SESSION_DRIVER', 'cookie'),
17
18 /*
19 |--------------------------------------------------------------------------
20 | Cookie Name
21 |--------------------------------------------------------------------------
22 |
23 | The name of the cookie to be used for saving session id. Session ids
24 | are signed and encrypted.
25 |
26 */
27 cookieName: 'adonis-session',
28
29 /*
30 |--------------------------------------------------------------------------
31 | Clear session when browser closes
32 |--------------------------------------------------------------------------
33 |
34 | If this value is true, the session cookie will be temporary and will be
35 | removed when browser closes.
36 |
37 */
38 clearWithBrowser: true,
39
40 /*
41 |--------------------------------------------------------------------------
42 | Session age
43 |--------------------------------------------------------------------------
44 |
45 | This value is only used when `clearWithBrowser` is set to false. The
46 | age must be a valid https://npmjs.org/package/ms string or should
47 | be in milliseconds.
48 |
49 | Valid values are:
50 | '2h', '10d', '5y', '2.5 hrs'
51 |
52 */
53 age: '2h',
54
55 /*
56 |--------------------------------------------------------------------------
57 | Cookie options
58 |--------------------------------------------------------------------------
59 |
60 | Cookie options defines the options to be used for setting up session
61 | cookie
62 |
63 */
64 cookie: {
65 httpOnly: true,
66 path: '/',
67 sameSite: false,
68 },
69
70 /*
71 |--------------------------------------------------------------------------
72 | Sessions location
73 |--------------------------------------------------------------------------
74 |
75 | If driver is set to file, we need to define the relative location from
76 | the temporary path or absolute url to any location.
77 |
78 */
79 file: {
80 location: 'sessions',
81 },
82
83 /*
84 |--------------------------------------------------------------------------
85 | Redis config
86 |--------------------------------------------------------------------------
87 |
88 | The configuration for the redis driver.
89 |
90 */
91 redis: {
92 host: '127.0.0.1',
93 port: 6379,
94 password: null,
95 db: 0,
96 keyPrefix: '',
97 },
98};
diff --git a/src/server/config/shield.js b/src/server/config/shield.js
deleted file mode 100644
index 5c1c5cd73..000000000
--- a/src/server/config/shield.js
+++ /dev/null
@@ -1,144 +0,0 @@
1
2module.exports = {
3 /*
4 |--------------------------------------------------------------------------
5 | Content Security Policy
6 |--------------------------------------------------------------------------
7 |
8 | Content security policy filters out the origins not allowed to execute
9 | and load resources like scripts, styles and fonts. There are wide
10 | variety of options to choose from.
11 */
12 csp: {
13 /*
14 |--------------------------------------------------------------------------
15 | Directives
16 |--------------------------------------------------------------------------
17 |
18 | All directives are defined in camelCase and here is the list of
19 | available directives and their possible values.
20 |
21 | https://content-security-policy.com
22 |
23 | @example
24 | directives: {
25 | defaultSrc: ['self', '@nonce', 'cdnjs.cloudflare.com']
26 | }
27 |
28 */
29 directives: {
30 },
31 /*
32 |--------------------------------------------------------------------------
33 | Report only
34 |--------------------------------------------------------------------------
35 |
36 | Setting `reportOnly=true` will not block the scripts from running and
37 | instead report them to a URL.
38 |
39 */
40 reportOnly: false,
41 /*
42 |--------------------------------------------------------------------------
43 | Set all headers
44 |--------------------------------------------------------------------------
45 |
46 | Headers staring with `X` have been depreciated, since all major browsers
47 | supports the standard CSP header. So its better to disable deperciated
48 | headers, unless you want them to be set.
49 |
50 */
51 setAllHeaders: false,
52
53 /*
54 |--------------------------------------------------------------------------
55 | Disable on android
56 |--------------------------------------------------------------------------
57 |
58 | Certain versions of android are buggy with CSP policy. So you can set
59 | this value to true, to disable it for Android versions with buggy
60 | behavior.
61 |
62 | Here is an issue reported on a different package, but helpful to read
63 | if you want to know the behavior. https://github.com/helmetjs/helmet/pull/82
64 |
65 */
66 disableAndroid: true,
67 },
68
69 /*
70 |--------------------------------------------------------------------------
71 | X-XSS-Protection
72 |--------------------------------------------------------------------------
73 |
74 | X-XSS Protection saves from applications from XSS attacks. It is adopted
75 | by IE and later followed by some other browsers.
76 |
77 | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
78 |
79 */
80 xss: {
81 enabled: true,
82 enableOnOldIE: false,
83 },
84
85 /*
86 |--------------------------------------------------------------------------
87 | Iframe Options
88 |--------------------------------------------------------------------------
89 |
90 | xframe defines whether or not your website can be embedded inside an
91 | iframe. Choose from one of the following options.
92 | @available options
93 | DENY, SAMEORIGIN, ALLOW-FROM http://example.com
94 |
95 | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
96 */
97 xframe: 'DENY',
98
99 /*
100 |--------------------------------------------------------------------------
101 | No Sniff
102 |--------------------------------------------------------------------------
103 |
104 | Browsers have a habit of sniffing content-type of a response. Which means
105 | files with .txt extension containing Javascript code will be executed as
106 | Javascript. You can disable this behavior by setting nosniff to false.
107 |
108 | Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
109 |
110 */
111 nosniff: true,
112
113 /*
114 |--------------------------------------------------------------------------
115 | No Open
116 |--------------------------------------------------------------------------
117 |
118 | IE users can execute webpages in the context of your website, which is
119 | a serious security risk. Below option will manage this for you.
120 |
121 */
122 noopen: true,
123
124 /*
125 |--------------------------------------------------------------------------
126 | CSRF Protection
127 |--------------------------------------------------------------------------
128 |
129 | CSRF Protection adds another layer of security by making sure, actionable
130 | routes does have a valid token to execute an action.
131 |
132 */
133 csrf: {
134 enable: true,
135 methods: ['POST', 'PUT', 'DELETE'],
136 filterUris: [],
137 cookieOptions: {
138 httpOnly: false,
139 sameSite: true,
140 path: '/',
141 maxAge: 7200,
142 },
143 },
144};
diff --git a/src/server/database/factory.js b/src/server/database/factory.js
deleted file mode 100644
index 550c5e6ab..000000000
--- a/src/server/database/factory.js
+++ /dev/null
@@ -1,20 +0,0 @@
1
2/*
3|--------------------------------------------------------------------------
4| Factory
5|--------------------------------------------------------------------------
6|
7| Factories are used to define blueprints for database tables or Lucid
8| models. Later you can use these blueprints to seed your database
9| with dummy data.
10|
11*/
12
13/** @type {import('@adonisjs/lucid/src/Factory')} */
14// const Factory = use('Factory')
15
16// Factory.blueprint('App/Models/User', (faker) => {
17// return {
18// username: faker.username()
19// }
20// })
diff --git a/src/server/database/migrations/1566385379883_service_schema.js b/src/server/database/migrations/1566385379883_service_schema.js
deleted file mode 100644
index 1db95c19d..000000000
--- a/src/server/database/migrations/1566385379883_service_schema.js
+++ /dev/null
@@ -1,22 +0,0 @@
1
2/** @type {import('@adonisjs/lucid/src/Schema')} */
3const Schema = use('Schema');
4
5class ServiceSchema extends Schema {
6 up() {
7 this.create('services', (table) => {
8 table.increments();
9 table.string('serviceId', 80).notNullable();
10 table.string('name', 80).notNullable();
11 table.string('recipeId', 254).notNullable();
12 table.json('settings');
13 table.timestamps();
14 });
15 }
16
17 down() {
18 this.drop('services');
19 }
20}
21
22module.exports = ServiceSchema;
diff --git a/src/server/database/migrations/1566554231482_recipe_schema.js b/src/server/database/migrations/1566554231482_recipe_schema.js
deleted file mode 100644
index 14fcb82e5..000000000
--- a/src/server/database/migrations/1566554231482_recipe_schema.js
+++ /dev/null
@@ -1,21 +0,0 @@
1
2/** @type {import('@adonisjs/lucid/src/Schema')} */
3const Schema = use('Schema');
4
5class RecipeSchema extends Schema {
6 up() {
7 this.create('recipes', (table) => {
8 table.increments();
9 table.string('name', 80).notNullable();
10 table.string('recipeId', 254).notNullable().unique();
11 table.json('data');
12 table.timestamps();
13 });
14 }
15
16 down() {
17 this.drop('recipes');
18 }
19}
20
21module.exports = RecipeSchema;
diff --git a/src/server/database/migrations/1566554359294_workspace_schema.js b/src/server/database/migrations/1566554359294_workspace_schema.js
deleted file mode 100644
index b53bbe656..000000000
--- a/src/server/database/migrations/1566554359294_workspace_schema.js
+++ /dev/null
@@ -1,23 +0,0 @@
1
2/** @type {import('@adonisjs/lucid/src/Schema')} */
3const Schema = use('Schema');
4
5class WorkspaceSchema extends Schema {
6 up() {
7 this.create('workspaces', (table) => {
8 table.increments();
9 table.string('workspaceId', 80).notNullable().unique();
10 table.string('name', 80).notNullable();
11 table.integer('order');
12 table.json('services');
13 table.json('data');
14 table.timestamps();
15 });
16 }
17
18 down() {
19 this.drop('workspaces');
20 }
21}
22
23module.exports = WorkspaceSchema;
diff --git a/src/server/database/template.sqlite b/src/server/database/template.sqlite
deleted file mode 100644
index db5425ee6..000000000
--- a/src/server/database/template.sqlite
+++ /dev/null
Binary files differ
diff --git a/src/server/env.ini b/src/server/env.ini
deleted file mode 100644
index 902e8e4c8..000000000
--- a/src/server/env.ini
+++ /dev/null
@@ -1,16 +0,0 @@
1HOST=127.0.0.1
2PORT=45569
3NODE_ENV=development
4APP_NAME=Ferdi Internal Server
5APP_URL=http://${HOST}:${PORT}
6CACHE_VIEWS=false
7APP_KEY=FERDIINTERNALSERVER
8DB_CONNECTION=sqlite
9DB_HOST=127.0.0.1
10DB_PORT=3306
11DB_USER=root
12DB_PASSWORD=
13DB_DATABASE=ferdi
14HASH_DRIVER=bcrypt
15IS_CREATION_ENABLED=true
16CONNECT_WITH_FRANZ=true \ No newline at end of file
diff --git a/src/server/logo.png b/src/server/logo.png
deleted file mode 100644
index 4145a077a..000000000
--- a/src/server/logo.png
+++ /dev/null
Binary files differ
diff --git a/src/server/package.json b/src/server/package.json
deleted file mode 100644
index 60dd58e93..000000000
--- a/src/server/package.json
+++ /dev/null
@@ -1,49 +0,0 @@
1{
2 "name": "ferdi-internal-server",
3 "version": "1.0.0",
4 "adonis-version": "4.1.0",
5 "description": "Internal server used by the Ferdi application.",
6 "main": "index.js",
7 "scripts": {
8 "start": "node server.js",
9 "test": "node ace test",
10 "lint": "eslint --fix ./"
11 },
12 "keywords": [
13 ],
14 "author": "",
15 "license": "MIT License",
16 "private": true,
17 "dependencies": {
18 "@adonisjs/ace": "^5.0.8",
19 "@adonisjs/auth": "^3.0.7",
20 "@adonisjs/bodyparser": "^2.0.5",
21 "@adonisjs/cors": "^1.0.7",
22 "@adonisjs/drive": "^1.0.4",
23 "@adonisjs/fold": "^4.0.9",
24 "@adonisjs/framework": "^5.0.9",
25 "@adonisjs/ignitor": "^2.0.8",
26 "@adonisjs/lucid": "^6.1.3",
27 "@adonisjs/session": "^1.0.29",
28 "@adonisjs/shield": "^1.0.8",
29 "@adonisjs/validator": "^5.0.6",
30 "atob": "^2.1.2",
31 "btoa": "^1.2.1",
32 "fs-extra": "^8.1.0",
33 "node-fetch": "^2.6.0",
34 "sqlite3": "^4.1.0",
35 "uuid": "^3.3.3"
36 },
37 "devDependencies": {
38 "eslint": "^6.3.0",
39 "eslint-config-airbnb": "^18.0.1",
40 "eslint-config-airbnb-base": "^14.0.0",
41 "eslint-plugin-import": "^2.18.2",
42 "eslint-plugin-jsx-a11y": "^6.2.3",
43 "eslint-plugin-react": "^7.14.3",
44 "eslint-plugin-react-hooks": "^1.7.0"
45 },
46 "autoload": {
47 "App": "./app"
48 }
49}
diff --git a/src/server/public/css/main.css b/src/server/public/css/main.css
deleted file mode 100644
index a1c5653d7..000000000
--- a/src/server/public/css/main.css
+++ /dev/null
@@ -1,69 +0,0 @@
1input {
2 margin-bottom: 1rem;
3 width: 100%;
4 padding: 0.5rem;
5}
6
7button, .button {
8 display: flex;
9 overflow: hidden;
10 padding: 12px 12px;
11 cursor: pointer;
12 width: 100%;
13 -webkit-user-select: none;
14 -moz-user-select: none;
15 -ms-user-select: none;
16 user-select: none;
17 transition: all 150ms linear;
18 text-align: center;
19 white-space: nowrap;
20 text-decoration: none !important;
21 text-transform: none;
22 text-transform: capitalize;
23 color: #fff !important;
24 border: 0 none;
25 border-radius: 4px;
26 font-size: 13px;
27 font-weight: 500;
28 line-height: 1.3;
29 -webkit-appearance: none;
30 -moz-appearance: none;
31 appearance: none;
32 justify-content: center;
33 align-items: center;
34 flex: 0 0 160px;
35 box-shadow: 2px 5px 10px #e4e4e4;
36 color: #FFFFFF;
37 background: #161616;
38}
39
40#dropzone {
41 width: 100%;
42 height: 30vh;
43 background-color: #ebebeb;
44
45 display: flex;
46 align-items: center;
47 justify-content: center;
48 text-align: center;
49
50 cursor: pointer;
51}
52
53#dropzone p {
54 font-size: 0.85rem;
55}
56
57#files {
58 display: none;
59}
60
61.alert {
62 background-color: #e7a8a6;
63 padding: 0.8rem;
64 margin-bottom: 1rem;
65}
66
67td {
68 word-break: break-all;
69} \ No newline at end of file
diff --git a/src/server/public/css/vanilla.css b/src/server/public/css/vanilla.css
deleted file mode 100644
index 37bc051a2..000000000
--- a/src/server/public/css/vanilla.css
+++ /dev/null
@@ -1,138 +0,0 @@
1/* Reset */
2html, body, div, span, applet, object, iframe,
3h1, h2, h3, h4, h5, h6, p, blockquote, pre,
4a, abbr, acronym, address, big, cite, code,
5del, dfn, em, img, ins, kbd, q, s, samp,
6small, strike, strong, sub, sup, tt, var,
7b, u, i, center,
8dl, dt, dd, ol, ul, li,
9fieldset, form, label, legend,
10table, caption, tbody, tfoot, thead, tr, th, td,
11article, aside, canvas, details, embed,
12figure, figcaption, footer, header, hgroup,
13menu, nav, output, ruby, section, summary,
14time, mark, audio, video {
15 margin: 0;
16 padding: 0;
17 border: 0;
18 font-size: 100%;
19 font: inherit;
20 vertical-align: baseline;
21}
22* {
23 box-sizing: border-box;
24}
25
26
27
28/* Variables */
29:root {
30 --desktop-font-size: 1.3rem/1.5;
31 --mobile-font-size: 1.1rem/1.4;
32 --text-color: #2d2d2d;
33 --link-color: blue;
34 --primary-color: lightsteelblue;
35 --secondary-color: aliceblue;
36 --tertiary-color: whitesmoke;
37}
38
39
40
41
42/* Typography */
43body {
44 color: var(--text-color);
45 padding: 3rem;
46 font: var(--desktop-font-size) -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto, Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji", "Segoe UI Symbol";
47}
48
49h1,h2,h3,h4,h5,h6,p,blockquote,dl,img,figure {
50 margin: 2rem 0;
51}
52
53h1,h2,h3,h4,h5,h6 { font-weight: bold; }
54h1 { font-size: 200%; }
55h2 { font-size: 150%; }
56h3 { font-size: 120%; }
57h4,h5,h6 { font-size: 100%; }
58h5, h6 { text-transform: uppercase; }
59
60header h1 { border-bottom: 1px solid; }
61
62p { margin: 2rem 0; }
63
64a,a:visited { color: var(--link-color); }
65
66strong, time, b { font-weight: bold; }
67em, dfn, i { font-style: italic; }
68sub { font-size: 60%; vertical-align: bottom; }
69small { font-size: 80%; }
70
71blockquote, q {
72 background: var(--secondary-color);
73 border-left: 10px solid var(--primary-color);
74 font-family: "Georgia", serif;
75 padding: 1rem;
76}
77blockquote p:first-child { margin-top: 0; }
78cite {
79 font-family: "Georgia", serif;
80 font-style: italic;
81 font-weight: bold;
82}
83
84kbd,code,samp,pre,var { font-family: monospace; font-weight: bold; }
85code, pre {
86 background: var(--tertiary-color);
87 padding: 0.5rem 1rem;
88}
89code pre , pre code { padding: 0; }
90
91
92
93/* Elements */
94hr {
95 background: var(--text-color);
96 border: 0;
97 height: 1px;
98 margin: 4rem 0;
99}
100
101img { max-width: 100%; }
102
103figure {
104 border: 1px solid var(--primary-color);
105 display: inline-block;
106 padding: 1rem;
107 width: auto;
108}
109figure img { margin: 0; }
110figure figcaption { font-size: 80%; }
111
112ul, ol { margin: 2rem 0; padding: 0 0 0 4rem; }
113
114dl dd { padding-left: 2rem; }
115
116table {
117 border: 1px solid var(--primary-color);
118 border-collapse: collapse;
119 table-layout: fixed;
120 width: 100%;
121}
122table caption { margin: 2rem 0; }
123table thead { text-align: center; }
124table tbody { text-align: right; }
125table tr { border-bottom: 1px solid var(--primary-color); }
126table tbody tr:nth-child(even) { background: var(--tertiary-color); }
127table th { background: var(--secondary-color); font-weight: bold; }
128table th, table td { padding: 1rem; }
129table th:not(last-of-type), table td:not(last-of-type) { border-right: 1px solid var(--primary-color); }
130
131
132
133/* Mobile Styling */
134@media screen and (max-width: 50rem) {
135 body {
136 font: var(--mobile-font-size) -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto, Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji", "Segoe UI Symbol"
137 }
138} \ No newline at end of file
diff --git a/src/server/resources/views/import.edge b/src/server/resources/views/import.edge
deleted file mode 100644
index 561021a0c..000000000
--- a/src/server/resources/views/import.edge
+++ /dev/null
@@ -1,18 +0,0 @@
1@layout('layouts.main')
2
3@section('content')
4<h1>Import a Franz account</h1>
5<p>Please login using your Franz account. We will import your services and workspaces.</p>
6<form action="import" method="post">
7 <label for="email">E-Mail address</label><br />
8 <input type="email" name="email" placeholder="joe@example.com" required><br />
9
10 <label for="password">Password</label><br />
11 <input type="password" name="password" placeholder="********" required><br />
12
13 <label for="server">API Server to import from</label><br />
14 <input type="text" name="server" value="https://api.franzinfra.com" required><br />
15
16 <button type="submit" id="submitbutton">Import Franz account</button>
17</form>
18@endsection
diff --git a/src/server/resources/views/index.edge b/src/server/resources/views/index.edge
deleted file mode 100644
index 3e0198a09..000000000
--- a/src/server/resources/views/index.edge
+++ /dev/null
@@ -1,32 +0,0 @@
1@layout('layouts.main')
2
3@section('content')
4<style>
5 ol,
6 p {
7 margin: 0.5rem 0;
8 }
9
10</style>
11<h1>Internal Ferdi Server</h1>
12<p>You are accessing the local server instance of your Ferdi application. This server is used to enable Ferdi's "Use without an Account" feature.</p>
13<p>
14 To use this server in your Ferdi client, <a href="ferdi://settings/app">open Ferdi's settings</a> and as the
15 <code>server</code>, enter <code id="server"></code>
16</p>
17<p>
18 Alternatively, you can <a href="/import">import your Franz account</a>.
19</p>
20
21<script>
22 // Get server URL for current location
23 let server = location.href.replace('/index.html', '');
24 if (server[server.length - 1] == '/') {
25 server = server.substr(0, server.length - 1)
26 }
27
28 // Show on page
29 document.getElementById('server').innerText = server;
30
31</script>
32@endsection
diff --git a/src/server/resources/views/layouts/main.edge b/src/server/resources/views/layouts/main.edge
deleted file mode 100644
index 77af30327..000000000
--- a/src/server/resources/views/layouts/main.edge
+++ /dev/null
@@ -1,18 +0,0 @@
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <meta http-equiv="X-UA-Compatible" content="ie=edge">
8 <title>ferdi-internal-server</title>
9
10 {{ style('css/vanilla') }}
11 {{ style('css/main') }}
12</head>
13
14<body>
15 @!section('content')
16</body>
17
18</html>
diff --git a/src/server/start.js b/src/server/start.js
deleted file mode 100644
index 34b2cb5fa..000000000
--- a/src/server/start.js
+++ /dev/null
@@ -1,41 +0,0 @@
1
2/*
3|--------------------------------------------------------------------------
4| Http server
5|--------------------------------------------------------------------------
6|
7| This file bootstraps Adonisjs to start the HTTP server. You are free to
8| customize the process of booting the http server.
9|
10| """ Loading ace commands """
11| At times you may want to load ace commands when starting the HTTP server.
12| Same can be done by chaining `loadCommands()` method after
13|
14| """ Preloading files """
15| Also you can preload files by calling `preLoad('path/to/file')` method.
16| Make sure to pass a relative path from the project root.
17*/
18const path = require('path');
19const fs = require('fs-extra');
20
21process.env.ENV_PATH = path.join(__dirname, 'env.ini');
22
23const { Ignitor } = require('@adonisjs/ignitor');
24const fold = require('@adonisjs/fold');
25
26module.exports = (dbPath, port) => {
27 if (!fs.existsSync(dbPath)) {
28 fs.copySync(
29 path.join(__dirname, 'database', 'template.sqlite'),
30 dbPath,
31 );
32 }
33
34 process.env.DB_PATH = dbPath;
35 process.env.PORT = port;
36
37 new Ignitor(fold)
38 .appRoot(__dirname)
39 .fireHttpServer()
40 .catch(console.error); // eslint-disable-line no-console
41};
diff --git a/src/server/start/app.js b/src/server/start/app.js
deleted file mode 100644
index a29ca6594..000000000
--- a/src/server/start/app.js
+++ /dev/null
@@ -1,62 +0,0 @@
1
2/*
3|--------------------------------------------------------------------------
4| Providers
5|--------------------------------------------------------------------------
6|
7| Providers are building blocks for your Adonis app. Anytime you install
8| a new Adonis specific package, chances are you will register the
9| provider here.
10|
11*/
12const providers = [
13 '@adonisjs/framework/providers/AppProvider',
14 '@adonisjs/bodyparser/providers/BodyParserProvider',
15 '@adonisjs/cors/providers/CorsProvider',
16 '@adonisjs/lucid/providers/LucidProvider',
17 '@adonisjs/drive/providers/DriveProvider',
18 '@adonisjs/validator/providers/ValidatorProvider',
19 '@adonisjs/framework/providers/ViewProvider',
20 '@adonisjs/shield/providers/ShieldProvider',
21];
22
23/*
24|--------------------------------------------------------------------------
25| Ace Providers
26|--------------------------------------------------------------------------
27|
28| Ace providers are required only when running ace commands. For example
29| Providers for migrations, tests etc.
30|
31*/
32const aceProviders = [
33 '@adonisjs/lucid/providers/MigrationsProvider',
34];
35
36/*
37|--------------------------------------------------------------------------
38| Aliases
39|--------------------------------------------------------------------------
40|
41| Aliases are short unique names for IoC container bindings. You are free
42| to create your own aliases.
43|
44| For example:
45| { Route: 'Adonis/Src/Route' }
46|
47*/
48const aliases = {};
49
50/*
51|--------------------------------------------------------------------------
52| Commands
53|--------------------------------------------------------------------------
54|
55| Here you store ace commands for your package
56|
57*/
58const commands = [];
59
60module.exports = {
61 providers, aceProviders, aliases, commands,
62};
diff --git a/src/server/start/kernel.js b/src/server/start/kernel.js
deleted file mode 100644
index 54fe1f35d..000000000
--- a/src/server/start/kernel.js
+++ /dev/null
@@ -1,56 +0,0 @@
1
2/** @type {import('@adonisjs/framework/src/Server')} */
3const Server = use('Server');
4
5/*
6|--------------------------------------------------------------------------
7| Global Middleware
8|--------------------------------------------------------------------------
9|
10| Global middleware are executed on each http request only when the routes
11| match.
12|
13*/
14const globalMiddleware = [
15 'Adonis/Middleware/BodyParser',
16 'App/Middleware/ConvertEmptyStringsToNull',
17];
18
19/*
20|--------------------------------------------------------------------------
21| Named Middleware
22|--------------------------------------------------------------------------
23|
24| Named middleware is key/value object to conditionally add middleware on
25| specific routes or group of routes.
26|
27| // define
28| {
29| auth: 'Adonis/Middleware/Auth'
30| }
31|
32| // use
33| Route.get().middleware('auth')
34|
35*/
36const namedMiddleware = {
37};
38
39/*
40|--------------------------------------------------------------------------
41| Server Middleware
42|--------------------------------------------------------------------------
43|
44| Server level middleware are executed even when route for a given URL is
45| not registered. Features like `static assets` and `cors` needs better
46| control over request lifecycle.
47|
48*/
49const serverMiddleware = [
50 'Adonis/Middleware/Static',
51];
52
53Server
54 .registerGlobal(globalMiddleware)
55 .registerNamed(namedMiddleware)
56 .use(serverMiddleware);
diff --git a/src/server/start/routes.js b/src/server/start/routes.js
deleted file mode 100644
index 333a5ba06..000000000
--- a/src/server/start/routes.js
+++ /dev/null
@@ -1,74 +0,0 @@
1
2/*
3|--------------------------------------------------------------------------
4| Routes
5|--------------------------------------------------------------------------
6|
7*/
8
9/** @type {typeof import('@adonisjs/framework/src/Route/Manager')} */
10const Route = use('Route');
11
12const OnlyAllowFerdi = async ({ request, response }, next) => {
13 const user = request.header('User-Agent');
14 if (!/Ferdi\/\d(\.\d){2}/g.test(user)) {
15 return response.status(403).redirect('/');
16 }
17
18 await next();
19 return true;
20};
21
22// Health: Returning if all systems function correctly
23Route.get('health', ({
24 response,
25}) => response.send({
26 api: 'success',
27 db: 'success',
28})).middleware(OnlyAllowFerdi);
29
30// API is grouped under '/v1/' route
31Route.group(() => {
32 // User authentification
33 Route.post('auth/signup', 'UserController.signup');
34 Route.post('auth/login', 'UserController.login');
35
36 // User info
37 Route.get('me', 'UserController.me');
38
39 // Service info
40 Route.post('service', 'ServiceController.create');
41 Route.put('service/:id', 'ServiceController.edit');
42 Route.delete('service/:id', 'ServiceController.delete');
43 Route.get('me/services', 'ServiceController.list');
44 Route.put('service/reorder', 'ServiceController.reorder');
45 Route.get('recipe', 'ServiceController.list');
46 Route.post('recipes/update', 'ServiceController.update');
47
48 // Recipe store
49 Route.get('recipes', 'RecipeController.list');
50 Route.get('recipes/download/:recipe', 'RecipeController.download');
51 Route.get('recipes/search', 'RecipeController.search');
52 Route.get('recipes/popular', 'StaticController.popularRecipes');
53 Route.get('recipes/update', 'StaticController.emptyArray');
54
55 // Workspaces
56 Route.put('workspace/:id', 'WorkspaceController.edit');
57 Route.delete('workspace/:id', 'WorkspaceController.delete');
58 Route.post('workspace', 'WorkspaceController.create');
59 Route.get('workspace', 'WorkspaceController.list');
60
61 // Static responses
62 Route.get('features', 'StaticController.features');
63 Route.get('services', 'StaticController.emptyArray');
64 Route.get('news', 'StaticController.emptyArray');
65 Route.get('payment/plans', 'StaticController.plans');
66 Route.get('announcements/:version', 'StaticController.announcement');
67}).prefix('v1').middleware(OnlyAllowFerdi);
68
69// Franz account import
70Route.post('import', 'UserController.import');
71Route.get('import', ({ view }) => view.render('import'));
72
73// Index
74Route.get('/', ({ view }) => view.render('index'));