diff options
author | vantezzen <properly@protonmail.com> | 2019-08-22 11:12:36 +0200 |
---|---|---|
committer | vantezzen <properly@protonmail.com> | 2019-08-22 11:12:36 +0200 |
commit | b018adf240679ec59a7344e30be39400f1ecd8af (patch) | |
tree | c076635761667dad302716b498088f1047281e46 /app | |
download | ferdium-server-b018adf240679ec59a7344e30be39400f1ecd8af.tar.gz ferdium-server-b018adf240679ec59a7344e30be39400f1ecd8af.tar.zst ferdium-server-b018adf240679ec59a7344e30be39400f1ecd8af.zip |
Initial commit
Diffstat (limited to 'app')
-rw-r--r-- | app/Controllers/Http/ServiceController.js | 94 | ||||
-rw-r--r-- | app/Controllers/Http/StaticController.js | 224 | ||||
-rw-r--r-- | app/Controllers/Http/UserController.js | 94 | ||||
-rw-r--r-- | app/Middleware/ConvertEmptyStringsToNull.js | 17 | ||||
-rw-r--r-- | app/Models/Service.js | 12 | ||||
-rw-r--r-- | app/Models/Token.js | 9 | ||||
-rw-r--r-- | app/Models/Traits/NoTimestamp.js | 16 | ||||
-rw-r--r-- | app/Models/User.js | 43 |
8 files changed, 509 insertions, 0 deletions
diff --git a/app/Controllers/Http/ServiceController.js b/app/Controllers/Http/ServiceController.js new file mode 100644 index 0000000..47a8a6f --- /dev/null +++ b/app/Controllers/Http/ServiceController.js | |||
@@ -0,0 +1,94 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const User = use('App/Models/User'); | ||
4 | const Service = use('App/Models/Service'); | ||
5 | |||
6 | class ServiceController { | ||
7 | // Create a new service 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 | const service = await Service.create({ | ||
22 | userId: auth.user.id, | ||
23 | name: data.name, | ||
24 | recipeId: data.recipeId, | ||
25 | settings: JSON.stringify(data) | ||
26 | }); | ||
27 | |||
28 | return response.send({ | ||
29 | "data": { | ||
30 | userId: auth.user.id, | ||
31 | id: service.id, | ||
32 | "isEnabled": true, | ||
33 | "isNotificationEnabled": true, | ||
34 | "isBadgeEnabled": true, | ||
35 | "isMuted": false, | ||
36 | "isDarkModeEnabled": "", | ||
37 | "spellcheckerLanguage": "", | ||
38 | "order": 1, | ||
39 | "customRecipe": false, | ||
40 | "hasCustomIcon": false, | ||
41 | "workspaces": [], | ||
42 | "iconUrl": null, | ||
43 | ...data, | ||
44 | }, | ||
45 | "status": ["created"] | ||
46 | }) | ||
47 | } | ||
48 | |||
49 | // List all services a user has created | ||
50 | async list({ | ||
51 | request, | ||
52 | response, | ||
53 | auth | ||
54 | }) { | ||
55 | try { | ||
56 | await auth.getUser() | ||
57 | } catch (error) { | ||
58 | return response.send('Missing or invalid api token') | ||
59 | } | ||
60 | |||
61 | |||
62 | const services = (await auth.user.services().fetch()).rows; | ||
63 | // Convert to array with all data Franz wants | ||
64 | const servicesArray = services.map(service => ({ | ||
65 | "customRecipe": false, | ||
66 | "hasCustomIcon": false, | ||
67 | "isBadgeEnabled": true, | ||
68 | "isDarkModeEnabled": "", | ||
69 | "isEnabled": true, | ||
70 | "isMuted": false, | ||
71 | "isNotificationEnabled": true, | ||
72 | "order": 1, | ||
73 | "spellcheckerLanguage": "", | ||
74 | "workspaces": [], | ||
75 | "iconUrl": null, | ||
76 | ...JSON.parse(service.settings), | ||
77 | "id": service.id, | ||
78 | "name": service.name, | ||
79 | "recipeId": service.recipeId, | ||
80 | "userId": auth.user.id, | ||
81 | })) | ||
82 | |||
83 | return response.send(servicesArray) | ||
84 | } | ||
85 | |||
86 | // Download a recipe (currently simply redirects to Franz's API) | ||
87 | download({ request, response, params }) { | ||
88 | const service = params.recipe; | ||
89 | |||
90 | response.redirect('https://api.franzinfra.com/v1/recipes/download/' + service) | ||
91 | } | ||
92 | } | ||
93 | |||
94 | module.exports = ServiceController | ||
diff --git a/app/Controllers/Http/StaticController.js b/app/Controllers/Http/StaticController.js new file mode 100644 index 0000000..70f2d7f --- /dev/null +++ b/app/Controllers/Http/StaticController.js | |||
@@ -0,0 +1,224 @@ | |||
1 | 'use strict' | ||
2 | /** | ||
3 | * Controller for routes with static responses | ||
4 | */ | ||
5 | |||
6 | class 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({ response, params }) { | ||
218 | return response.send({ | ||
219 | 'en-US': 'You are using an unofficial Franz Server.' | ||
220 | }); | ||
221 | } | ||
222 | } | ||
223 | |||
224 | module.exports = StaticController | ||
diff --git a/app/Controllers/Http/UserController.js b/app/Controllers/Http/UserController.js new file mode 100644 index 0000000..88f7ecd --- /dev/null +++ b/app/Controllers/Http/UserController.js | |||
@@ -0,0 +1,94 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const User = use('App/Models/User'); | ||
4 | const atob = require('atob'); | ||
5 | |||
6 | class UserController { | ||
7 | |||
8 | // Register a new user | ||
9 | async signup({ | ||
10 | request, | ||
11 | response, | ||
12 | auth, | ||
13 | session | ||
14 | }) { | ||
15 | const data = request.only(['firstname', 'email', 'password']); | ||
16 | const user = await User.create({ | ||
17 | email: data.email, | ||
18 | password: data.password, | ||
19 | username: data.firstname | ||
20 | }); | ||
21 | const token = await auth.generate(user) | ||
22 | |||
23 | return response.send({ | ||
24 | "message": "Successfully created account", | ||
25 | "token": token.token | ||
26 | }); | ||
27 | } | ||
28 | |||
29 | // Login using an existing user | ||
30 | async login({ | ||
31 | request, | ||
32 | response, | ||
33 | auth | ||
34 | }) { | ||
35 | const authHeader = atob(request.header('Authorization')).split(':'); | ||
36 | |||
37 | let user = (await User.query().where('email', authHeader[0]).limit(1).fetch()).toJSON(); | ||
38 | if (!user[0] || !user[0].email) { | ||
39 | return response.status(401).send({ | ||
40 | "message": "User credentials not valid", | ||
41 | "code": "invalid-credentials", | ||
42 | "status": 401 | ||
43 | }); | ||
44 | } | ||
45 | |||
46 | let token; | ||
47 | try { | ||
48 | // TODO: Login is currently not working as the password is incorrect | ||
49 | token = await auth.attempt(user[0].id, authHeader[1]) | ||
50 | } catch (e) { | ||
51 | return response.status(401).send({ | ||
52 | "message": "User credentials not valid", | ||
53 | "code": "invalid-credentials", | ||
54 | "status": 401 | ||
55 | }); | ||
56 | } | ||
57 | |||
58 | return response.send({ | ||
59 | "message": "Successfully logged in", | ||
60 | "token": token.token | ||
61 | }); | ||
62 | } | ||
63 | |||
64 | // Return information about the current user | ||
65 | async me({ | ||
66 | request, | ||
67 | response, | ||
68 | auth, | ||
69 | session | ||
70 | }) { | ||
71 | try { | ||
72 | await auth.getUser() | ||
73 | } catch (error) { | ||
74 | response.send('Missing or invalid api token') | ||
75 | } | ||
76 | |||
77 | return response.send({ | ||
78 | accountType: "individual", | ||
79 | beta: false, | ||
80 | donor: {}, | ||
81 | email: auth.user.email, | ||
82 | emailValidated: true, | ||
83 | features: {}, | ||
84 | firstname: "Franz", | ||
85 | id: "2acd2aa0-0869-4a91-adab-f700ac256dbe", | ||
86 | isPremium: true, | ||
87 | isSubscriptionOwner: true, | ||
88 | lastname: "Franz", | ||
89 | locale: "en-US" | ||
90 | }); | ||
91 | } | ||
92 | } | ||
93 | |||
94 | module.exports = UserController | ||
diff --git a/app/Middleware/ConvertEmptyStringsToNull.js b/app/Middleware/ConvertEmptyStringsToNull.js new file mode 100644 index 0000000..a5750cc --- /dev/null +++ b/app/Middleware/ConvertEmptyStringsToNull.js | |||
@@ -0,0 +1,17 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | class ConvertEmptyStringsToNull { | ||
4 | async handle ({ request }, next) { | ||
5 | if (Object.keys(request.body).length) { | ||
6 | request.body = Object.assign( | ||
7 | ...Object.keys(request.body).map(key => ({ | ||
8 | [key]: request.body[key] !== '' ? request.body[key] : null | ||
9 | })) | ||
10 | ) | ||
11 | } | ||
12 | |||
13 | await next() | ||
14 | } | ||
15 | } | ||
16 | |||
17 | module.exports = ConvertEmptyStringsToNull | ||
diff --git a/app/Models/Service.js b/app/Models/Service.js new file mode 100644 index 0000000..0ca72fd --- /dev/null +++ b/app/Models/Service.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 Service extends Model { | ||
7 | user() { | ||
8 | return this.belongsTo('App/Models/User', 'userId', 'id') | ||
9 | } | ||
10 | } | ||
11 | |||
12 | module.exports = Service | ||
diff --git a/app/Models/Token.js b/app/Models/Token.js new file mode 100644 index 0000000..e089e87 --- /dev/null +++ b/app/Models/Token.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 Token extends Model { | ||
7 | } | ||
8 | |||
9 | module.exports = Token | ||
diff --git a/app/Models/Traits/NoTimestamp.js b/app/Models/Traits/NoTimestamp.js new file mode 100644 index 0000000..edd07f0 --- /dev/null +++ b/app/Models/Traits/NoTimestamp.js | |||
@@ -0,0 +1,16 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | class NoTimestamp { | ||
4 | register (Model) { | ||
5 | Object.defineProperties(Model, { | ||
6 | createdAtColumn: { | ||
7 | get: () => null, | ||
8 | }, | ||
9 | updatedAtColumn: { | ||
10 | get: () => null, | ||
11 | } | ||
12 | }) | ||
13 | } | ||
14 | } | ||
15 | |||
16 | module.exports = NoTimestamp | ||
diff --git a/app/Models/User.js b/app/Models/User.js new file mode 100644 index 0000000..0bb1547 --- /dev/null +++ b/app/Models/User.js | |||
@@ -0,0 +1,43 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | /** @type {typeof import('@adonisjs/lucid/src/Lucid/Model')} */ | ||
4 | const Model = use('Model') | ||
5 | |||
6 | /** @type {import('@adonisjs/framework/src/Hash')} */ | ||
7 | const Hash = use('Hash') | ||
8 | |||
9 | class User extends Model { | ||
10 | static boot () { | ||
11 | super.boot() | ||
12 | |||
13 | /** | ||
14 | * A hook to hash the user password before saving | ||
15 | * it to the database. | ||
16 | */ | ||
17 | this.addHook('beforeSave', async (userInstance) => { | ||
18 | if (userInstance.dirty.password) { | ||
19 | userInstance.password = await Hash.make(userInstance.password) | ||
20 | } | ||
21 | }) | ||
22 | } | ||
23 | |||
24 | /** | ||
25 | * A relationship on tokens is required for auth to | ||
26 | * work. Since features like `refreshTokens` or | ||
27 | * `rememberToken` will be saved inside the | ||
28 | * tokens table. | ||
29 | * | ||
30 | * @method tokens | ||
31 | * | ||
32 | * @return {Object} | ||
33 | */ | ||
34 | tokens () { | ||
35 | return this.hasMany('App/Models/Token') | ||
36 | } | ||
37 | |||
38 | services () { | ||
39 | return this.hasMany('App/Models/Service', 'id', 'userId') | ||
40 | } | ||
41 | } | ||
42 | |||
43 | module.exports = User | ||