diff options
-rw-r--r-- | app/Controllers/Http/RecipeController.js | 27 | ||||
-rw-r--r-- | app/Controllers/Http/ServiceController.js | 2 | ||||
-rw-r--r-- | app/Controllers/Http/WorkspaceController.js | 116 | ||||
-rw-r--r-- | app/Models/Recipe.js | 9 | ||||
-rw-r--r-- | app/Models/User.js | 4 | ||||
-rw-r--r-- | app/Models/Workspace.js | 12 | ||||
-rw-r--r-- | database/migrations/1566554231482_recipe_schema.js | 22 | ||||
-rw-r--r-- | database/migrations/1566554359294_workspace_schema.js | 25 | ||||
-rw-r--r-- | reqs.txt | 5 | ||||
-rw-r--r-- | start/routes.js | 18 |
10 files changed, 234 insertions, 6 deletions
diff --git a/app/Controllers/Http/RecipeController.js b/app/Controllers/Http/RecipeController.js new file mode 100644 index 0000000..0b9d488 --- /dev/null +++ b/app/Controllers/Http/RecipeController.js | |||
@@ -0,0 +1,27 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const Recipe = use('App/Models/Recipe'); | ||
4 | const fetch = require('node-fetch'); | ||
5 | |||
6 | class RecipeController { | ||
7 | async list({ | ||
8 | response | ||
9 | }) { | ||
10 | const officialRecipes = JSON.parse(await (await fetch('https://api.franzinfra.com/v1/recipes')).text()); | ||
11 | const customRecipesArray = (await Recipe.all()).rows; | ||
12 | const customRecipes = customRecipesArray.map(recipe => ({ | ||
13 | "id": recipe.recipeId, | ||
14 | "name": recipe.name, | ||
15 | ...JSON.parse(recipe.data) | ||
16 | })) | ||
17 | |||
18 | const recipes = [ | ||
19 | ...officialRecipes, | ||
20 | ...customRecipes, | ||
21 | ] | ||
22 | |||
23 | return response.send(recipes) | ||
24 | } | ||
25 | } | ||
26 | |||
27 | module.exports = RecipeController | ||
diff --git a/app/Controllers/Http/ServiceController.js b/app/Controllers/Http/ServiceController.js index 0fcbec0..4c908ac 100644 --- a/app/Controllers/Http/ServiceController.js +++ b/app/Controllers/Http/ServiceController.js | |||
@@ -23,7 +23,7 @@ class ServiceController { | |||
23 | let serviceId; | 23 | let serviceId; |
24 | do { | 24 | do { |
25 | serviceId = uuid(); | 25 | serviceId = uuid(); |
26 | } while((await Service.all()).rows.length > 0) | 26 | } while((await Service.query().where('serviceId', serviceId).fetch()).rows.length > 0) |
27 | 27 | ||
28 | const service = await Service.create({ | 28 | const service = await Service.create({ |
29 | userId: auth.user.id, | 29 | userId: auth.user.id, |
diff --git a/app/Controllers/Http/WorkspaceController.js b/app/Controllers/Http/WorkspaceController.js new file mode 100644 index 0000000..55a0c75 --- /dev/null +++ b/app/Controllers/Http/WorkspaceController.js | |||
@@ -0,0 +1,116 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const Workspace = use('App/Models/Workspace'); | ||
4 | const uuid = require('uuid/v4'); | ||
5 | |||
6 | class WorkspaceController { | ||
7 | // Create a new workspace for user | ||
8 | async create({ | ||
9 | request, | ||
10 | response, | ||
11 | auth | ||
12 | }) { | ||
13 | try { | ||
14 | await auth.getUser() | ||
15 | } catch (error) { | ||
16 | return response.send('Missing or invalid api token') | ||
17 | } | ||
18 | |||
19 | const data = request.all(); | ||
20 | |||
21 | // Get new, unused uuid | ||
22 | let workspaceId; | ||
23 | do { | ||
24 | workspaceId = uuid(); | ||
25 | } while ((await Workspace.query().where('workspaceId', workspaceId).fetch()).rows.length > 0) | ||
26 | |||
27 | const order = (await auth.user.workspaces().fetch()).rows.length; | ||
28 | |||
29 | await Workspace.create({ | ||
30 | userId: auth.user.id, | ||
31 | workspaceId, | ||
32 | name: data.name, | ||
33 | order, | ||
34 | services: JSON.stringify([]), | ||
35 | data: JSON.stringify(data) | ||
36 | }); | ||
37 | |||
38 | return response.send({ | ||
39 | userId: auth.user.id, | ||
40 | name: data.name, | ||
41 | id: workspaceId, | ||
42 | order, | ||
43 | workspaces: [], | ||
44 | }) | ||
45 | } | ||
46 | |||
47 | async edit({ | ||
48 | request, | ||
49 | response, | ||
50 | auth, | ||
51 | params | ||
52 | }) { | ||
53 | try { | ||
54 | await auth.getUser() | ||
55 | } catch (error) { | ||
56 | return response.send('Missing or invalid api token') | ||
57 | } | ||
58 | |||
59 | const data = request.all(); | ||
60 | const { | ||
61 | id | ||
62 | } = params; | ||
63 | |||
64 | // Update data in database | ||
65 | await (Workspace.query() | ||
66 | .where('workspaceId', id) | ||
67 | .where('userId', auth.user.id)).update({ | ||
68 | name: data.name, | ||
69 | services: JSON.stringify(data.services) | ||
70 | }); | ||
71 | |||
72 | // Get updated row | ||
73 | const workspace = (await Workspace.query() | ||
74 | .where('workspaceId', id) | ||
75 | .where('userId', auth.user.id).fetch()).rows[0]; | ||
76 | |||
77 | return response.send({ | ||
78 | "id": workspace.workspaceId, | ||
79 | "name": data.name, | ||
80 | "order": workspace.order, | ||
81 | "services": data.services, | ||
82 | "userId": auth.user.id | ||
83 | }) | ||
84 | } | ||
85 | |||
86 | // List all workspaces a user has created | ||
87 | async list({ | ||
88 | request, | ||
89 | response, | ||
90 | auth | ||
91 | }) { | ||
92 | try { | ||
93 | await auth.getUser() | ||
94 | } catch (error) { | ||
95 | return response.send('Missing or invalid api token') | ||
96 | } | ||
97 | |||
98 | const workspaces = (await auth.user.workspaces().fetch()).rows; | ||
99 | // Convert to array with all data Franz wants | ||
100 | let workspacesArray = []; | ||
101 | if(workspaces) { | ||
102 | workspacesArray = workspaces.map(workspace => ({ | ||
103 | "id": workspace.workspaceId, | ||
104 | "name": workspace.name, | ||
105 | "order": workspace.order, | ||
106 | "services": JSON.parse(workspace.services), | ||
107 | "userId": auth.user.id | ||
108 | })) | ||
109 | } | ||
110 | |||
111 | |||
112 | return response.send(workspacesArray) | ||
113 | } | ||
114 | } | ||
115 | |||
116 | module.exports = WorkspaceController | ||
diff --git a/app/Models/Recipe.js b/app/Models/Recipe.js new file mode 100644 index 0000000..9e3619c --- /dev/null +++ b/app/Models/Recipe.js | |||
@@ -0,0 +1,9 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ | ||
4 | const Model = use('Model') | ||
5 | |||
6 | class Recipe extends Model { | ||
7 | } | ||
8 | |||
9 | module.exports = Recipe | ||
diff --git a/app/Models/User.js b/app/Models/User.js index 0bb1547..c9a680a 100644 --- a/app/Models/User.js +++ b/app/Models/User.js | |||
@@ -38,6 +38,10 @@ class User extends Model { | |||
38 | services () { | 38 | services () { |
39 | return this.hasMany('App/Models/Service', 'id', 'userId') | 39 | return this.hasMany('App/Models/Service', 'id', 'userId') |
40 | } | 40 | } |
41 | |||
42 | workspaces () { | ||
43 | return this.hasMany('App/Models/Workspace', 'id', 'userId') | ||
44 | } | ||
41 | } | 45 | } |
42 | 46 | ||
43 | module.exports = User | 47 | module.exports = User |
diff --git a/app/Models/Workspace.js b/app/Models/Workspace.js new file mode 100644 index 0000000..f78a3f9 --- /dev/null +++ b/app/Models/Workspace.js | |||
@@ -0,0 +1,12 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ | ||
4 | const Model = use('Model') | ||
5 | |||
6 | class Workspace extends Model { | ||
7 | user() { | ||
8 | return this.belongsTo('App/Models/User', 'userId', 'id') | ||
9 | } | ||
10 | } | ||
11 | |||
12 | module.exports = Workspace | ||
diff --git a/database/migrations/1566554231482_recipe_schema.js b/database/migrations/1566554231482_recipe_schema.js new file mode 100644 index 0000000..7d8f5a8 --- /dev/null +++ b/database/migrations/1566554231482_recipe_schema.js | |||
@@ -0,0 +1,22 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | /** @type {import('@adonisjs/lucid/src/Schema')} */ | ||
4 | const Schema = use('Schema') | ||
5 | |||
6 | class RecipeSchema extends Schema { | ||
7 | up () { | ||
8 | this.create('recipes', (table) => { | ||
9 | table.increments() | ||
10 | table.string('name', 80).notNullable() | ||
11 | table.string('recipeId', 254).notNullable().unique() | ||
12 | table.json('data') | ||
13 | table.timestamps() | ||
14 | }) | ||
15 | } | ||
16 | |||
17 | down () { | ||
18 | this.drop('recipes') | ||
19 | } | ||
20 | } | ||
21 | |||
22 | module.exports = RecipeSchema | ||
diff --git a/database/migrations/1566554359294_workspace_schema.js b/database/migrations/1566554359294_workspace_schema.js new file mode 100644 index 0000000..84e0bb9 --- /dev/null +++ b/database/migrations/1566554359294_workspace_schema.js | |||
@@ -0,0 +1,25 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | /** @type {import('@adonisjs/lucid/src/Schema')} */ | ||
4 | const Schema = use('Schema') | ||
5 | |||
6 | class WorkspaceSchema extends Schema { | ||
7 | up () { | ||
8 | this.create('workspaces', (table) => { | ||
9 | table.increments() | ||
10 | table.string('workspaceId', 80).notNullable().unique() | ||
11 | table.string('userId', 80).notNullable() | ||
12 | table.string('name', 80).notNullable() | ||
13 | table.integer('order') | ||
14 | table.json('services') | ||
15 | table.json('data') | ||
16 | table.timestamps() | ||
17 | }) | ||
18 | } | ||
19 | |||
20 | down () { | ||
21 | this.drop('workspaces') | ||
22 | } | ||
23 | } | ||
24 | |||
25 | module.exports = WorkspaceSchema | ||
diff --git a/reqs.txt b/reqs.txt new file mode 100644 index 0000000..3940f02 --- /dev/null +++ b/reqs.txt | |||
@@ -0,0 +1,5 @@ | |||
1 | GET /recipes (with auth) // All recipes | ||
2 | [{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"discord","name":"Discord","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/discord/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/discord/src/icon.svg"}},{"author":"Brian Gilbert <brian@briangilbert.net>","featured":false,"id":"gitter","name":"Gitter","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/gitter/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/gitter/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"gmail","name":"Gmail","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/gmail/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/gmail/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"hangouts","name":"Hangouts","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/hangouts/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/hangouts/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"hipchat","name":"HipChat","version":"1.0.1","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/hipchat/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/hipchat/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"gmailinbox","name":"Inbox by Gmail","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/gmailinbox/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/gmailinbox/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"mattermost","name":"Mattermost","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/mattermost/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/mattermost/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"messenger","name":"Messenger","version":"1.0.6","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/messenger/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/messenger/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"mysms","name":"MySMS","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/mysms/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/mysms/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"rocketchat","name":"Rocket.Chat","version":"1.0.1","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/rocketchat/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/rocketchat/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"skype","name":"Skype","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/skype/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/skype/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"slack","name":"Slack","version":"1.0.4","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/slack/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/slack/src/icon.svg"}},{"author":"Brian Gilbert <brian@briangilbert.net>","featured":false,"id":"tawk","name":"Tawk.to","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/tawk/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/tawk/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"telegram","name":"Telegram","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/telegram/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/telegram/src/icon.svg"}},{"author":"Franz <recipe@meetfranz.com>","featured":false,"id":"toggl","name":"toggl","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/toggl/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/toggl/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"tweetdeck","name":"Tweetdeck","version":"1.0.1","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/tweetdeck/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/tweetdeck/src/icon.svg"}},{"author":"Stuart Clark <stuart@realityloop.com>","featured":false,"id":"twist","name":"twist","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/twist/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/twist/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"whatsapp","name":"WhatsApp","version":"1.0.1","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/whatsapp/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/whatsapp/src/icon.svg"}}] | ||
3 | |||
4 | GET /recipes/search?needle=query // Search recipes | ||
5 | [{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"gmail","name":"Gmail","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/gmail/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/gmail/src/icon.svg"}},{"author":"Stefan Malzner <stefan@adlk.io>","featured":false,"id":"gmailinbox","name":"Inbox by Gmail","version":"1.0.0","icons":{"png":"https://cdn.franzinfra.com/recipes/dist/gmailinbox/src/icon.png","svg":"https://cdn.franzinfra.com/recipes/dist/gmailinbox/src/icon.svg"}}] \ No newline at end of file | ||
diff --git a/start/routes.js b/start/routes.js index ef688c8..da12a1b 100644 --- a/start/routes.js +++ b/start/routes.js | |||
@@ -29,19 +29,27 @@ Route.group(() => { | |||
29 | // User info | 29 | // User info |
30 | Route.get('me', 'UserController.me').middleware('auth') | 30 | Route.get('me', 'UserController.me').middleware('auth') |
31 | 31 | ||
32 | // Service/recipe info | 32 | // Service info |
33 | Route.post('service', 'ServiceController.create').middleware('auth') | 33 | Route.post('service', 'ServiceController.create').middleware('auth') |
34 | Route.get('me/services', 'ServiceController.list').middleware('auth') | 34 | Route.get('me/services', 'ServiceController.list').middleware('auth') |
35 | Route.get('recipe', 'ServiceController.list').middleware('auth') | ||
36 | |||
37 | // Recipe store | ||
38 | Route.get('recipes', 'RecipeController.list') | ||
35 | Route.get('recipes/download/:recipe', 'ServiceController.download') | 39 | Route.get('recipes/download/:recipe', 'ServiceController.download') |
40 | Route.get('recipes/popular', 'StaticController.popularRecipes') | ||
41 | Route.get('recipes/update', 'StaticController.emptyArray') | ||
42 | |||
43 | // Workspaces | ||
44 | Route.put('workspace/:id', 'WorkspaceController.edit').middleware('auth') | ||
45 | Route.post('workspace', 'WorkspaceController.create').middleware('auth') | ||
46 | Route.get('workspace', 'WorkspaceController.list').middleware('auth') | ||
36 | 47 | ||
37 | // Static responses | 48 | // Static responses |
38 | Route.get('features', 'StaticController.features'); | 49 | Route.get('features', 'StaticController.features') |
39 | Route.get('services', 'StaticController.emptyArray') | 50 | Route.get('services', 'StaticController.emptyArray') |
40 | Route.get('workspace', 'StaticController.emptyArray') | ||
41 | Route.get('news', 'StaticController.emptyArray') | 51 | Route.get('news', 'StaticController.emptyArray') |
42 | Route.get('payment/plans', 'StaticController.plans') | 52 | Route.get('payment/plans', 'StaticController.plans') |
43 | Route.get('recipes/popular', 'StaticController.popularRecipes') | ||
44 | Route.get('recipes/update', 'StaticController.emptyArray') | ||
45 | // Route.get('announcements/:version', 'StaticController.announcement') | 53 | // Route.get('announcements/:version', 'StaticController.announcement') |
46 | }).prefix('v1') | 54 | }).prefix('v1') |
47 | 55 | ||