aboutsummaryrefslogtreecommitdiffstats
path: root/src/server
diff options
context:
space:
mode:
authorLibravatar vantezzen <hello@vantezzen.io>2019-10-14 17:42:24 +0200
committerLibravatar vantezzen <hello@vantezzen.io>2019-10-14 17:42:24 +0200
commit37c2448778e6595dc09291e46c4ba7dca89eb20d (patch)
tree5a389311df5cb1dc813c93cde62d633c1bd99bb0 /src/server
parentAdd local server (diff)
downloadferdium-app-37c2448778e6595dc09291e46c4ba7dca89eb20d.tar.gz
ferdium-app-37c2448778e6595dc09291e46c4ba7dca89eb20d.tar.zst
ferdium-app-37c2448778e6595dc09291e46c4ba7dca89eb20d.zip
Develop internal server
Diffstat (limited to 'src/server')
-rw-r--r--src/server/README.md35
-rw-r--r--src/server/app/Controllers/Http/DashboardController.js38
-rw-r--r--src/server/app/Controllers/Http/RecipeController.js91
-rw-r--r--src/server/app/Exceptions/Handler.js2
-rw-r--r--src/server/app/Models/User.js2
-rw-r--r--src/server/database/ferdi.sqlitebin36864 -> 0 bytes
-rw-r--r--src/server/logo.pngbin340668 -> 298671 bytes
-rw-r--r--src/server/public/js/new.js24
-rw-r--r--src/server/resources/views/import.edge (renamed from src/server/resources/views/others/import.edge)6
-rw-r--r--src/server/resources/views/index.edge (renamed from src/server/resources/views/others/index.edge)12
-rw-r--r--src/server/resources/views/others/new.edge40
-rw-r--r--src/server/start/routes.js13
12 files changed, 11 insertions, 252 deletions
diff --git a/src/server/README.md b/src/server/README.md
index 833d9643e..0074f2314 100644
--- a/src/server/README.md
+++ b/src/server/README.md
@@ -5,23 +5,8 @@
5# ferdi-internal-server 5# ferdi-internal-server
6Internal Ferdi Server used for storing settings without logging into an external server. 6Internal Ferdi Server used for storing settings without logging into an external server.
7 7
8npm i @adonisjs/ace @adonisjs/auth @adonisjs/bodyparser @adonisjs/cors @adonisjs/drive @adonisjs/fold @adonisjs/framework @adonisjs/ignitor @adonisjs/lucid @adonisjs/session @adonisjs/shield @adonisjs/validator atob btoa fs-extra node-fetch sqlite3 uuid targz
9
10### Manual setup
111. Clone this repository
122. Install the [AdonisJS CLI](https://adonisjs.com/)
133. Run the database migrations with
14 ```js
15 adonis migration:run
16 ```
174. Start the server with
18 ```js
19 adonis serve --dev
20 ```
21
22## Configuration 8## Configuration
23franz-server's configuration is saved inside the `.env` file. Besides AdonisJS's settings, ferdi-server has the following custom settings: 9franz-server's configuration is saved inside the `env.ini` file. Besides AdonisJS's settings, ferdi-internal-server has the following custom settings:
24- `IS_CREATION_ENABLED` (`true` or `false`, default: `true`): Whether to enable the [creation of custom recipes](#creating-and-using-custom-recipes)
25- `CONNECT_WITH_FRANZ` (`true` or `false`, default: `true`): Whether to enable connections to the Franz server. By enabling this option, ferdi-server can: 10- `CONNECT_WITH_FRANZ` (`true` or `false`, default: `true`): Whether to enable connections to the Franz server. By enabling this option, ferdi-server can:
26 - Show the full Franz recipe library instead of only custom recipes 11 - Show the full Franz recipe library instead of only custom recipes
27 - Import Franz accounts 12 - Import Franz accounts
@@ -30,21 +15,3 @@ franz-server's configuration is saved inside the `.env` file. Besides AdonisJS's
30ferdi-server allows you to import your full Franz account, including all its settings. 15ferdi-server allows you to import your full Franz account, including all its settings.
31 16
32To 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. 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.
33
34## Creating and using custom recipes
35ferdi-server allows to extends the Franz recipe catalogue with custom Ferdi recipes.
36
37For documentation on how to create a recipe, please visit [the official guide by Franz](https://github.com/meetfranz/plugins/blob/master/docs/integration.md).
38
39To add your recipe to ferdi-server, open `http://localhost:45569/new` in your browser. You can now define the following settings:
40- `Author`: Author who created the recipe
41- `Name`: Name for your new service. Can contain spaces and unicode characters
42- `Service ID`: Unique ID for this recipe. Does not contain spaces or special characters (e.g. `google-drive`)
43- `Link to PNG/SVG image`: Direct link to a 1024x1024 PNG image and SVG that is used as a logo inside the store. Please use jsDelivr when using a file uploaded to GitHub as raw.githubusercontent files won't load
44- `Recipe files`: Recipe files that you created using the [Franz recipe creation guide](https://github.com/meetfranz/plugins/blob/master/docs/integration.md). Please do *not* package your files beforehand - upload the raw files (you can drag and drop multiple files). ferdi-server will automatically package and store the recipe in the right format. Please also do not drag and drop or select the whole folder, select the individual files.
45
46### Listing custom recipes
47Inside Ferdi, searching for `ferdi:custom` will list all your custom recipes.
48
49## License
50ferdi-server is licensed under the MIT License
diff --git a/src/server/app/Controllers/Http/DashboardController.js b/src/server/app/Controllers/Http/DashboardController.js
deleted file mode 100644
index 69af16227..000000000
--- a/src/server/app/Controllers/Http/DashboardController.js
+++ /dev/null
@@ -1,38 +0,0 @@
1class DashboardController {
2 async data({
3 auth,
4 view,
5 }) {
6 const general = auth.user;
7 const services = (await auth.user.services().fetch()).toJSON();
8 const workspaces = (await auth.user.workspaces().fetch()).toJSON();
9
10 return view.render('dashboard.data', {
11 username: general.username,
12 mail: general.email,
13 created: general.created_at,
14 updated: general.updated_at,
15 services,
16 workspaces,
17 });
18 }
19
20 logout({
21 auth,
22 response,
23 }) {
24 auth.authenticator('session').logout();
25 return response.redirect('/user/login');
26 }
27
28 delete({
29 auth,
30 response,
31 }) {
32 auth.user.delete();
33 auth.authenticator('session').logout();
34 return response.redirect('/user/login');
35 }
36}
37
38module.exports = DashboardController;
diff --git a/src/server/app/Controllers/Http/RecipeController.js b/src/server/app/Controllers/Http/RecipeController.js
index 5ed21122a..c887ea00e 100644
--- a/src/server/app/Controllers/Http/RecipeController.js
+++ b/src/server/app/Controllers/Http/RecipeController.js
@@ -8,29 +8,15 @@ const {
8const Env = use('Env'); 8const Env = use('Env');
9 9
10const fetch = require('node-fetch'); 10const fetch = require('node-fetch');
11const targz = require('targz');
12const path = require('path'); 11const path = require('path');
13const fs = require('fs-extra'); 12const fs = require('fs-extra');
14 13
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 { 14class RecipeController {
29 // List official and custom recipes 15 // List official and custom recipes
30 async list({ 16 async list({
31 response, 17 response,
32 }) { 18 }) {
33 const officialRecipes = JSON.parse(await (await fetch('https://api.franzinfra.com/v1/recipes')).text()); 19 const officialRecipes = JSON.parse(await (await fetch('https://api.getferdi.com/v1/recipes')).text());
34 const customRecipesArray = (await Recipe.all()).rows; 20 const customRecipesArray = (await Recipe.all()).rows;
35 const customRecipes = customRecipesArray.map(recipe => ({ 21 const customRecipes = customRecipesArray.map(recipe => ({
36 id: recipe.recipeId, 22 id: recipe.recipeId,
@@ -46,77 +32,6 @@ class RecipeController {
46 return response.send(recipes); 32 return response.send(recipes);
47 } 33 }
48 34
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 35 // Search official and custom recipes
121 async search({ 36 async search({
122 request, 37 request,
@@ -149,7 +64,7 @@ class RecipeController {
149 } else { 64 } else {
150 let remoteResults = []; 65 let remoteResults = [];
151 if (Env.get('CONNECT_WITH_FRANZ') == 'true') { // eslint-disable-line eqeqeq 66 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()); 67 remoteResults = JSON.parse(await (await fetch(`https://api.getferdi.com/v1/recipes/search?needle=${encodeURIComponent(needle)}`)).text());
153 } 68 }
154 const localResultsArray = (await Recipe.query().where('name', 'LIKE', `%${needle}%`).fetch()).toJSON(); 69 const localResultsArray = (await Recipe.query().where('name', 'LIKE', `%${needle}%`).fetch()).toJSON();
155 const localResults = localResultsArray.map(recipe => ({ 70 const localResults = localResultsArray.map(recipe => ({
@@ -195,7 +110,7 @@ class RecipeController {
195 if (await Drive.exists(`${service}.tar.gz`)) { 110 if (await Drive.exists(`${service}.tar.gz`)) {
196 return response.send(await Drive.get(`${service}.tar.gz`)); 111 return response.send(await Drive.get(`${service}.tar.gz`));
197 } if (Env.get('CONNECT_WITH_FRANZ') == 'true') { // eslint-disable-line eqeqeq 112 } if (Env.get('CONNECT_WITH_FRANZ') == 'true') { // eslint-disable-line eqeqeq
198 return response.redirect(`https://api.franzinfra.com/v1/recipes/download/${service}`); 113 return response.redirect(`https://api.getferdi.com/v1/recipes/download/${service}`);
199 } 114 }
200 return response.status(400).send({ 115 return response.status(400).send({
201 message: 'Recipe not found', 116 message: 'Recipe not found',
diff --git a/src/server/app/Exceptions/Handler.js b/src/server/app/Exceptions/Handler.js
index cb9e10bbe..e8d2d2ee2 100644
--- a/src/server/app/Exceptions/Handler.js
+++ b/src/server/app/Exceptions/Handler.js
@@ -22,8 +22,6 @@ class ExceptionHandler extends BaseExceptionHandler {
22 async handle(error, { response }) { 22 async handle(error, { response }) {
23 if (error.name === 'ValidationException') { 23 if (error.name === 'ValidationException') {
24 return response.status(400).send('Invalid arguments'); 24 return response.status(400).send('Invalid arguments');
25 } if (error.name === 'InvalidSessionException') {
26 return response.status(401).redirect('/user/login');
27 } 25 }
28 26
29 return response.status(error.status).send(error.message); 27 return response.status(error.status).send(error.message);
diff --git a/src/server/app/Models/User.js b/src/server/app/Models/User.js
index 9783cbe45..907710d8d 100644
--- a/src/server/app/Models/User.js
+++ b/src/server/app/Models/User.js
@@ -1,4 +1,4 @@
1 1// File is required by AdonisJS but not used by the server
2/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ 2/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */
3const Model = use('Model'); 3const Model = use('Model');
4 4
diff --git a/src/server/database/ferdi.sqlite b/src/server/database/ferdi.sqlite
deleted file mode 100644
index db5425ee6..000000000
--- a/src/server/database/ferdi.sqlite
+++ /dev/null
Binary files differ
diff --git a/src/server/logo.png b/src/server/logo.png
index 587e0b86e..4145a077a 100644
--- a/src/server/logo.png
+++ b/src/server/logo.png
Binary files differ
diff --git a/src/server/public/js/new.js b/src/server/public/js/new.js
deleted file mode 100644
index beaa36da6..000000000
--- a/src/server/public/js/new.js
+++ /dev/null
@@ -1,24 +0,0 @@
1/* eslint-env browser */
2const elDrop = document.getElementById('dropzone');
3const submitBtn = document.getElementById('submitbutton');
4const fileInput = document.getElementById('files');
5
6elDrop.addEventListener('dragover', (event) => {
7 event.preventDefault();
8});
9
10elDrop.addEventListener('drop', async (event) => {
11 event.preventDefault();
12
13 submitBtn.disabled = true;
14
15 fileInput.files = event.dataTransfer.files;
16
17 elDrop.innerText = `✓ ${fileInput.files.length} files selected`;
18 elDrop.style.height = 'inherit';
19
20 submitBtn.disabled = false;
21});
22elDrop.addEventListener('click', () => {
23 fileInput.click();
24});
diff --git a/src/server/resources/views/others/import.edge b/src/server/resources/views/import.edge
index b890bbb2a..f7b52b179 100644
--- a/src/server/resources/views/others/import.edge
+++ b/src/server/resources/views/import.edge
@@ -2,7 +2,7 @@
2 2
3@section('content') 3@section('content')
4<h1>Import a Franz account</h1> 4<h1>Import a Franz account</h1>
5<p>Please login using your Franz account. We will create a new Ferdi account with the same credentials.</p> 5<p>Please login using your Franz account. We will import your services and workspaces.</p>
6<form action="import" method="post"> 6<form action="import" method="post">
7 <label for="email">E-Mail address</label><br /> 7 <label for="email">E-Mail address</label><br />
8 <input type="email" name="email" placeholder="joe@example.com" required><br /> 8 <input type="email" name="email" placeholder="joe@example.com" required><br />
@@ -11,9 +11,5 @@
11 <input type="password" name="password" placeholder="********" required><br /> 11 <input type="password" name="password" placeholder="********" required><br />
12 12
13 <button type="submit" id="submitbutton">Import Franz account</button> 13 <button type="submit" id="submitbutton">Import Franz account</button>
14 <small>
15 By importing your Franz account, you accept the <a href="/terms">Terms of service</a> and <a href="/privacy">Privacy
16 policy</a>
17 </small>
18</form> 14</form>
19@endsection 15@endsection
diff --git a/src/server/resources/views/others/index.edge b/src/server/resources/views/index.edge
index c594d3142..3e0198a09 100644
--- a/src/server/resources/views/others/index.edge
+++ b/src/server/resources/views/index.edge
@@ -8,22 +8,16 @@
8 } 8 }
9 9
10</style> 10</style>
11<h1>ferdi-server</h1> 11<h1>Internal Ferdi Server</h1>
12<p>You are accessing a custom <a href="https://github.com/kytwb/ferdi">Ferdi</a> server.</p> 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> 13<p>
14 To use this server in your Ferdi client, <a href="ferdi://settings/app">open Ferdi's settings</a> and as the 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> 15 <code>server</code>, enter <code id="server"></code>
16</p> 16</p>
17<p> 17<p>
18 Alternatively, you can manage your account in the <a href="/user/account">account dashboard</a>. 18 Alternatively, you can <a href="/import">import your Franz account</a>.
19</p> 19</p>
20 20
21<br />
22<small>
23 <a href="https://github.com/vantezzen/ferdi-server">ferdi-server</a> is a project by <a
24 href="https://vantezzen.io">vantezzen</a>.
25</small>
26
27<script> 21<script>
28 // Get server URL for current location 22 // Get server URL for current location
29 let server = location.href.replace('/index.html', ''); 23 let server = location.href.replace('/index.html', '');
diff --git a/src/server/resources/views/others/new.edge b/src/server/resources/views/others/new.edge
deleted file mode 100644
index 1b54558fc..000000000
--- a/src/server/resources/views/others/new.edge
+++ /dev/null
@@ -1,40 +0,0 @@
1@layout('layouts.main')
2
3@section('content')
4<h1>Create a new recipe</h1>
5<p>Please create a recipe using <a href="https://github.com/meetfranz/plugins/blob/master/docs/integration.md">the
6 official Franz guide</a>, then publish it here.</p>
7<form action="new" method="post" enctype="multipart/form-data">
8 <label for="author">Author</label><br />
9 <input type="text" name="author" placeholder="Jon Doe" required><br />
10
11 <label for="name">Name</label><br />
12 <input type="text" name="name" placeholder="Sample Service" required><br />
13
14 <label for="id">Service ID</label><br />
15 <input type="text" name="id" placeholder="sample-service" required><br />
16
17 <label for="png">Link to PNG image*</label><br />
18 <input type="text" name="png" placeholder="https://.../logo.png" required><br />
19
20 <label for="svg">Link to SVG image*</label><br />
21 <input type="text" name="svg" placeholder="https://.../logo.svg" required><br />
22 *These images must be publicly availible and have CORS enabled in order to work.<br /><br />
23
24 <label for="package">Recipe files</label><br />
25 <div id="dropzone" effectAllowed="move">
26 <div>
27 Drop recipe files here<br />or click here to select files
28 <p>
29 Drag and drop your recipe files into this area.<br />
30 Please do not select the folder that contains the files but rather the files itself.
31 </p>
32 </div>
33 </div>
34 <input type="file" name="files[]" id="files" value="" multiple required><br /><br />
35
36 <button type="submit" id="submitbutton">Create recipe</button>
37</form>
38
39<script src="js/new.js"></script>
40@endsection
diff --git a/src/server/start/routes.js b/src/server/start/routes.js
index f3896bfa3..ec2e79a7c 100644
--- a/src/server/start/routes.js
+++ b/src/server/start/routes.js
@@ -57,18 +57,9 @@ Route.group(() => {
57 Route.get('announcements/:version', 'StaticController.announcement'); 57 Route.get('announcements/:version', 'StaticController.announcement');
58}).prefix('v1'); 58}).prefix('v1');
59 59
60// Recipe creation
61Route.post('new', 'RecipeController.create');
62Route.get('new', ({ response, view }) => {
63 if (Env.get('IS_CREATION_ENABLED') == 'false') { // eslint-disable-line eqeqeq
64 return response.send('This server doesn\'t allow the creation of new recipes.\n\nIf you are the server owner, please set IS_CREATION_ENABLED to true to enable recipe creation.');
65 }
66 return view.render('others.new');
67});
68
69// Franz account import 60// Franz account import
70Route.post('import', 'UserController.import'); 61Route.post('import', 'UserController.import');
71Route.get('import', ({ view }) => view.render('others.import')); 62Route.get('import', ({ view }) => view.render('import'));
72 63
73// Index 64// Index
74Route.get('/', ({ view }) => view.render('others.index')); 65Route.get('/', ({ view }) => view.render('index'));