From 5970b8e5bbf993c88c1f901708a7c5075a916770 Mon Sep 17 00:00:00 2001 From: vantezzen Date: Fri, 23 Aug 2019 14:04:22 +0200 Subject: Add support for workspaces --- app/Controllers/Http/RecipeController.js | 27 +++++ app/Controllers/Http/ServiceController.js | 2 +- app/Controllers/Http/WorkspaceController.js | 116 +++++++++++++++++++++ app/Models/Recipe.js | 9 ++ app/Models/User.js | 4 + app/Models/Workspace.js | 12 +++ database/migrations/1566554231482_recipe_schema.js | 22 ++++ .../migrations/1566554359294_workspace_schema.js | 25 +++++ reqs.txt | 5 + start/routes.js | 18 +++- 10 files changed, 234 insertions(+), 6 deletions(-) create mode 100644 app/Controllers/Http/RecipeController.js create mode 100644 app/Controllers/Http/WorkspaceController.js create mode 100644 app/Models/Recipe.js create mode 100644 app/Models/Workspace.js create mode 100644 database/migrations/1566554231482_recipe_schema.js create mode 100644 database/migrations/1566554359294_workspace_schema.js create mode 100644 reqs.txt 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 @@ +'use strict' + +const Recipe = use('App/Models/Recipe'); +const fetch = require('node-fetch'); + +class RecipeController { + async list({ + response + }) { + const officialRecipes = JSON.parse(await (await fetch('https://api.franzinfra.com/v1/recipes')).text()); + const customRecipesArray = (await Recipe.all()).rows; + const customRecipes = customRecipesArray.map(recipe => ({ + "id": recipe.recipeId, + "name": recipe.name, + ...JSON.parse(recipe.data) + })) + + const recipes = [ + ...officialRecipes, + ...customRecipes, + ] + + return response.send(recipes) + } +} + +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 { let serviceId; do { serviceId = uuid(); - } while((await Service.all()).rows.length > 0) + } while((await Service.query().where('serviceId', serviceId).fetch()).rows.length > 0) const service = await Service.create({ 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 @@ +'use strict' + +const Workspace = use('App/Models/Workspace'); +const uuid = require('uuid/v4'); + +class WorkspaceController { + // Create a new workspace for user + async create({ + request, + response, + auth + }) { + try { + await auth.getUser() + } catch (error) { + return response.send('Missing or invalid api token') + } + + const data = request.all(); + + // Get new, unused uuid + let workspaceId; + do { + workspaceId = uuid(); + } while ((await Workspace.query().where('workspaceId', workspaceId).fetch()).rows.length > 0) + + const order = (await auth.user.workspaces().fetch()).rows.length; + + await Workspace.create({ + userId: auth.user.id, + workspaceId, + name: data.name, + order, + services: JSON.stringify([]), + data: JSON.stringify(data) + }); + + return response.send({ + userId: auth.user.id, + name: data.name, + id: workspaceId, + order, + workspaces: [], + }) + } + + async edit({ + request, + response, + auth, + params + }) { + try { + await auth.getUser() + } catch (error) { + return response.send('Missing or invalid api token') + } + + const data = request.all(); + const { + id + } = params; + + // Update data in database + await (Workspace.query() + .where('workspaceId', id) + .where('userId', auth.user.id)).update({ + name: data.name, + services: JSON.stringify(data.services) + }); + + // Get updated row + const workspace = (await Workspace.query() + .where('workspaceId', id) + .where('userId', auth.user.id).fetch()).rows[0]; + + return response.send({ + "id": workspace.workspaceId, + "name": data.name, + "order": workspace.order, + "services": data.services, + "userId": auth.user.id + }) + } + + // List all workspaces a user has created + async list({ + request, + response, + auth + }) { + try { + await auth.getUser() + } catch (error) { + return response.send('Missing or invalid api token') + } + + const workspaces = (await auth.user.workspaces().fetch()).rows; + // Convert to array with all data Franz wants + let workspacesArray = []; + if(workspaces) { + workspacesArray = workspaces.map(workspace => ({ + "id": workspace.workspaceId, + "name": workspace.name, + "order": workspace.order, + "services": JSON.parse(workspace.services), + "userId": auth.user.id + })) + } + + + return response.send(workspacesArray) + } +} + +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 @@ +'use strict' + +/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ +const Model = use('Model') + +class Recipe extends Model { +} + +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 { services () { return this.hasMany('App/Models/Service', 'id', 'userId') } + + workspaces () { + return this.hasMany('App/Models/Workspace', 'id', 'userId') + } } 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 @@ +'use strict' + +/** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ +const Model = use('Model') + +class Workspace extends Model { + user() { + return this.belongsTo('App/Models/User', 'userId', 'id') + } +} + +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 @@ +'use strict' + +/** @type {import('@adonisjs/lucid/src/Schema')} */ +const Schema = use('Schema') + +class RecipeSchema extends Schema { + up () { + this.create('recipes', (table) => { + table.increments() + table.string('name', 80).notNullable() + table.string('recipeId', 254).notNullable().unique() + table.json('data') + table.timestamps() + }) + } + + down () { + this.drop('recipes') + } +} + +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 @@ +'use strict' + +/** @type {import('@adonisjs/lucid/src/Schema')} */ +const Schema = use('Schema') + +class WorkspaceSchema extends Schema { + up () { + this.create('workspaces', (table) => { + table.increments() + table.string('workspaceId', 80).notNullable().unique() + table.string('userId', 80).notNullable() + table.string('name', 80).notNullable() + table.integer('order') + table.json('services') + table.json('data') + table.timestamps() + }) + } + + down () { + this.drop('workspaces') + } +} + +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 @@ +GET /recipes (with auth) // All recipes +[{"author":"Stefan Malzner ","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 ","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 ","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 ","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 ","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 ","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 ","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 ","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 ","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 ","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 ","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 ","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 ","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 ","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 ","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 ","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 ","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 ","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"}}] + +GET /recipes/search?needle=query // Search recipes +[{"author":"Stefan Malzner ","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 ","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(() => { // User info Route.get('me', 'UserController.me').middleware('auth') - // Service/recipe info + // Service info Route.post('service', 'ServiceController.create').middleware('auth') Route.get('me/services', 'ServiceController.list').middleware('auth') + Route.get('recipe', 'ServiceController.list').middleware('auth') + + // Recipe store + Route.get('recipes', 'RecipeController.list') Route.get('recipes/download/:recipe', 'ServiceController.download') + Route.get('recipes/popular', 'StaticController.popularRecipes') + Route.get('recipes/update', 'StaticController.emptyArray') + + // Workspaces + Route.put('workspace/:id', 'WorkspaceController.edit').middleware('auth') + Route.post('workspace', 'WorkspaceController.create').middleware('auth') + Route.get('workspace', 'WorkspaceController.list').middleware('auth') // Static responses - Route.get('features', 'StaticController.features'); + Route.get('features', 'StaticController.features') Route.get('services', 'StaticController.emptyArray') - Route.get('workspace', 'StaticController.emptyArray') Route.get('news', 'StaticController.emptyArray') Route.get('payment/plans', 'StaticController.plans') - Route.get('recipes/popular', 'StaticController.popularRecipes') - Route.get('recipes/update', 'StaticController.emptyArray') // Route.get('announcements/:version', 'StaticController.announcement') }).prefix('v1') -- cgit v1.2.3-54-g00ecf