diff options
author | MCMXC <16797721+mcmxcdev@users.noreply.github.com> | 2024-02-10 18:19:14 -0700 |
---|---|---|
committer | Vijay A <vraravam@users.noreply.github.com> | 2024-02-17 21:51:20 +0530 |
commit | a61e73c33b2e80d5af58e3dcfa2efe74245cd065 (patch) | |
tree | d5343ad245b4156662920896dedf14d9284051f8 /app/Controllers/Http | |
parent | upgrade node to 20.11.1 and other minor versions for pkgs (#104) (diff) | |
download | ferdium-server-a61e73c33b2e80d5af58e3dcfa2efe74245cd065.tar.gz ferdium-server-a61e73c33b2e80d5af58e3dcfa2efe74245cd065.tar.zst ferdium-server-a61e73c33b2e80d5af58e3dcfa2efe74245cd065.zip |
refactor: project maintenance
- work in progress
Diffstat (limited to 'app/Controllers/Http')
20 files changed, 484 insertions, 589 deletions
diff --git a/app/Controllers/Http/Api/Static/AnnouncementsController.ts b/app/Controllers/Http/Api/Static/AnnouncementsController.ts index c20707b..4ae9d0e 100644 --- a/app/Controllers/Http/Api/Static/AnnouncementsController.ts +++ b/app/Controllers/Http/Api/Static/AnnouncementsController.ts | |||
@@ -1,20 +1,16 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | import Application from '@ioc:Adonis/Core/Application'; | 2 | import { app } from '@adonisjs/core/services/app' |
3 | import path from 'node:path'; | 3 | import path from 'node:path' |
4 | import fs from 'fs-extra'; | 4 | import fs from 'fs-extra' |
5 | 5 | ||
6 | export default class AnnouncementsController { | 6 | export default class AnnouncementsController { |
7 | public async show({ response, params }: HttpContextContract) { | 7 | public async show({ response, params }: HttpContext) { |
8 | const announcement = path.join( | 8 | const announcement = path.join(app.resourcesPath(), 'announcements', `${params.version}.json`) |
9 | Application.resourcesPath(), | ||
10 | 'announcements', | ||
11 | `${params.version}.json`, | ||
12 | ); | ||
13 | 9 | ||
14 | if (await fs.pathExists(announcement)) { | 10 | if (await fs.pathExists(announcement)) { |
15 | return response.download(announcement); | 11 | return response.download(announcement) |
16 | } | 12 | } |
17 | 13 | ||
18 | return response.status(404).send('No announcement found.'); | 14 | return response.status(404).send('No announcement found.') |
19 | } | 15 | } |
20 | } | 16 | } |
diff --git a/app/Controllers/Http/Api/Static/EmptyController.ts b/app/Controllers/Http/Api/Static/EmptyController.ts index a07790e..ff05b1c 100644 --- a/app/Controllers/Http/Api/Static/EmptyController.ts +++ b/app/Controllers/Http/Api/Static/EmptyController.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | 2 | ||
3 | export default class EmptyController { | 3 | export default class EmptyController { |
4 | public async show({ response }: HttpContextContract) { | 4 | public async show({ response }: HttpContext) { |
5 | return response.send([]); | 5 | return response.send([]) |
6 | } | 6 | } |
7 | } | 7 | } |
diff --git a/app/Controllers/Http/Api/Static/FeaturesController.ts b/app/Controllers/Http/Api/Static/FeaturesController.ts index d471b11..9e14c10 100644 --- a/app/Controllers/Http/Api/Static/FeaturesController.ts +++ b/app/Controllers/Http/Api/Static/FeaturesController.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | 2 | ||
3 | export default class FeaturesController { | 3 | export default class FeaturesController { |
4 | public async show({ response }: HttpContextContract) { | 4 | public async show({ response }: HttpContext) { |
5 | return response.send({ | 5 | return response.send({ |
6 | isServiceProxyEnabled: true, | 6 | isServiceProxyEnabled: true, |
7 | isWorkspaceEnabled: true, | 7 | isWorkspaceEnabled: true, |
@@ -9,6 +9,6 @@ export default class FeaturesController { | |||
9 | isSettingsWSEnabled: false, | 9 | isSettingsWSEnabled: false, |
10 | isMagicBarEnabled: true, | 10 | isMagicBarEnabled: true, |
11 | isTodosEnabled: true, | 11 | isTodosEnabled: true, |
12 | }); | 12 | }) |
13 | } | 13 | } |
14 | } | 14 | } |
diff --git a/app/Controllers/Http/Dashboard/AccountController.ts b/app/Controllers/Http/Dashboard/AccountController.ts index 3c4e919..5870f19 100644 --- a/app/Controllers/Http/Dashboard/AccountController.ts +++ b/app/Controllers/Http/Dashboard/AccountController.ts | |||
@@ -1,29 +1,23 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | import { schema, rules, validator } from '@ioc:Adonis/Core/Validator'; | 2 | import { schema, rules, validator } from '@adonisjs/validator' |
3 | import crypto from 'node:crypto'; | 3 | import crypto from 'node:crypto' |
4 | 4 | ||
5 | export default class AccountController { | 5 | export default class AccountController { |
6 | /** | 6 | /** |
7 | * Shows the user account page | 7 | * Shows the user account page |
8 | */ | 8 | */ |
9 | public async show({ auth, view }: HttpContextContract) { | 9 | public async show({ auth, view }: HttpContext) { |
10 | return view.render('dashboard/account', { | 10 | return view.render('dashboard/account', { |
11 | username: auth.user?.username, | 11 | username: auth.user?.username, |
12 | email: auth.user?.email, | 12 | email: auth.user?.email, |
13 | lastname: auth.user?.lastname, | 13 | lastname: auth.user?.lastname, |
14 | }); | 14 | }) |
15 | } | 15 | } |
16 | 16 | ||
17 | /** | 17 | /** |
18 | * Stores user account data | 18 | * Stores user account data |
19 | */ | 19 | */ |
20 | public async store({ | 20 | public async store({ auth, request, response, session, view }: HttpContext) { |
21 | auth, | ||
22 | request, | ||
23 | response, | ||
24 | session, | ||
25 | view, | ||
26 | }: HttpContextContract) { | ||
27 | try { | 21 | try { |
28 | await validator.validate({ | 22 | await validator.validate({ |
29 | schema: schema.create({ | 23 | schema: schema.create({ |
@@ -48,26 +42,26 @@ export default class AccountController { | |||
48 | lastname: schema.string([rules.required()]), | 42 | lastname: schema.string([rules.required()]), |
49 | }), | 43 | }), |
50 | data: request.only(['username', 'email', 'lastname']), | 44 | data: request.only(['username', 'email', 'lastname']), |
51 | }); | 45 | }) |
52 | } catch (error) { | 46 | } catch (error) { |
53 | session.flash(error.messages); | 47 | session.flash(error.messages) |
54 | return response.redirect('/user/account'); | 48 | return response.redirect('/user/account') |
55 | } | 49 | } |
56 | 50 | ||
57 | // Update user account | 51 | // Update user account |
58 | const { user } = auth; | 52 | const { user } = auth |
59 | if (user) { | 53 | if (user) { |
60 | user.username = request.input('username'); | 54 | user.username = request.input('username') |
61 | user.lastname = request.input('lastname'); | 55 | user.lastname = request.input('lastname') |
62 | user.email = request.input('email'); | 56 | user.email = request.input('email') |
63 | if (request.input('password')) { | 57 | if (request.input('password')) { |
64 | const hashedPassword = crypto | 58 | const hashedPassword = crypto |
65 | .createHash('sha256') | 59 | .createHash('sha256') |
66 | .update(request.input('password')) | 60 | .update(request.input('password')) |
67 | .digest('base64'); | 61 | .digest('base64') |
68 | user.password = hashedPassword; | 62 | user.password = hashedPassword |
69 | } | 63 | } |
70 | await user.save(); | 64 | await user.save() |
71 | } | 65 | } |
72 | 66 | ||
73 | return view.render('dashboard/account', { | 67 | return view.render('dashboard/account', { |
@@ -75,6 +69,6 @@ export default class AccountController { | |||
75 | lastname: user?.lastname, | 69 | lastname: user?.lastname, |
76 | email: user?.email, | 70 | email: user?.email, |
77 | success: user !== undefined, | 71 | success: user !== undefined, |
78 | }); | 72 | }) |
79 | } | 73 | } |
80 | } | 74 | } |
diff --git a/app/Controllers/Http/Dashboard/DataController.ts b/app/Controllers/Http/Dashboard/DataController.ts index f77702f..8a77329 100644 --- a/app/Controllers/Http/Dashboard/DataController.ts +++ b/app/Controllers/Http/Dashboard/DataController.ts | |||
@@ -1,14 +1,14 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | 2 | ||
3 | export default class DataController { | 3 | export default class DataController { |
4 | /** | 4 | /** |
5 | * Display the data page | 5 | * Display the data page |
6 | */ | 6 | */ |
7 | public async show({ view, auth }: HttpContextContract) { | 7 | public async show({ view, auth }: HttpContext) { |
8 | const { user } = auth; | 8 | const { user } = auth |
9 | 9 | ||
10 | const services = await user?.related('services').query(); | 10 | const services = await user?.related('services').query() |
11 | const workspaces = await user?.related('workspaces').query(); | 11 | const workspaces = await user?.related('workspaces').query() |
12 | 12 | ||
13 | return view.render('dashboard/data', { | 13 | return view.render('dashboard/data', { |
14 | username: user?.username, | 14 | username: user?.username, |
@@ -19,6 +19,6 @@ export default class DataController { | |||
19 | stringify: JSON.stringify, | 19 | stringify: JSON.stringify, |
20 | services, | 20 | services, |
21 | workspaces, | 21 | workspaces, |
22 | }); | 22 | }) |
23 | } | 23 | } |
24 | } | 24 | } |
diff --git a/app/Controllers/Http/Dashboard/DeleteController.ts b/app/Controllers/Http/Dashboard/DeleteController.ts index ef8188c..bd824b0 100644 --- a/app/Controllers/Http/Dashboard/DeleteController.ts +++ b/app/Controllers/Http/Dashboard/DeleteController.ts | |||
@@ -1,20 +1,20 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | 2 | ||
3 | export default class DeleteController { | 3 | export default class DeleteController { |
4 | /** | 4 | /** |
5 | * Display the delete page | 5 | * Display the delete page |
6 | */ | 6 | */ |
7 | public async show({ view }: HttpContextContract) { | 7 | public async show({ view }: HttpContext) { |
8 | return view.render('dashboard/delete'); | 8 | return view.render('dashboard/delete') |
9 | } | 9 | } |
10 | 10 | ||
11 | /** | 11 | /** |
12 | * Delete user and session | 12 | * Delete user and session |
13 | */ | 13 | */ |
14 | public async delete({ auth, response }: HttpContextContract) { | 14 | public async delete({ auth, response }: HttpContext) { |
15 | auth.user?.delete(); | 15 | auth.user?.delete() |
16 | auth.use('web').logout(); | 16 | auth.use('web').logout() |
17 | 17 | ||
18 | return response.redirect('/user/login'); | 18 | return response.redirect('/user/login') |
19 | } | 19 | } |
20 | } | 20 | } |
diff --git a/app/Controllers/Http/Dashboard/ExportController.ts b/app/Controllers/Http/Dashboard/ExportController.ts index 7155eab..5b6df70 100644 --- a/app/Controllers/Http/Dashboard/ExportController.ts +++ b/app/Controllers/Http/Dashboard/ExportController.ts | |||
@@ -1,33 +1,30 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | 2 | ||
3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | 3 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
4 | function deepParseToJSON(obj: any): Record<string, unknown> { | 4 | function deepParseToJSON(obj: any): Record<string, unknown> { |
5 | if (typeof obj !== 'object' || obj === null) { | 5 | if (typeof obj !== 'object' || obj === null) { |
6 | try { | 6 | try { |
7 | // Try to parse the object as JSON | 7 | // Try to parse the object as JSON |
8 | return JSON.parse(obj) as Record<string, unknown>; | 8 | return JSON.parse(obj) as Record<string, unknown> |
9 | } catch { | 9 | } catch { |
10 | // If parsing fails, return the original value | 10 | // If parsing fails, return the original value |
11 | return obj; | 11 | return obj |
12 | } | 12 | } |
13 | } | 13 | } |
14 | 14 | ||
15 | // If obj is an object, recursively parse its keys | 15 | // If obj is an object, recursively parse its keys |
16 | if (Array.isArray(obj)) { | 16 | if (Array.isArray(obj)) { |
17 | // If obj is an array, recursively parse each element | 17 | // If obj is an array, recursively parse each element |
18 | return obj.map(item => deepParseToJSON(item)) as unknown as Record< | 18 | return obj.map((item) => deepParseToJSON(item)) as unknown as Record<string, unknown> |
19 | string, | ||
20 | unknown | ||
21 | >; | ||
22 | } else { | 19 | } else { |
23 | // If obj is an object, recursively parse its keys | 20 | // If obj is an object, recursively parse its keys |
24 | const parsedObj: Record<string, unknown> = {}; | 21 | const parsedObj: Record<string, unknown> = {} |
25 | for (const key in obj) { | 22 | for (const key in obj) { |
26 | if (obj.hasOwnProperty(key)) { | 23 | if (obj.hasOwnProperty(key)) { |
27 | parsedObj[key] = deepParseToJSON(obj[key]); | 24 | parsedObj[key] = deepParseToJSON(obj[key]) |
28 | } | 25 | } |
29 | } | 26 | } |
30 | return parsedObj; | 27 | return parsedObj |
31 | } | 28 | } |
32 | } | 29 | } |
33 | 30 | ||
@@ -35,10 +32,10 @@ export default class ExportController { | |||
35 | /** | 32 | /** |
36 | * Display the export page | 33 | * Display the export page |
37 | */ | 34 | */ |
38 | public async show({ auth, response }: HttpContextContract) { | 35 | public async show({ auth, response }: HttpContext) { |
39 | const user = auth.user!; | 36 | const user = auth.user! |
40 | const services = await user.related('services').query(); | 37 | const services = await user.related('services').query() |
41 | const workspaces = await user.related('workspaces').query(); | 38 | const workspaces = await user.related('workspaces').query() |
42 | 39 | ||
43 | const exportData = { | 40 | const exportData = { |
44 | username: user.username, | 41 | username: user.username, |
@@ -46,11 +43,11 @@ export default class ExportController { | |||
46 | mail: user.email, | 43 | mail: user.email, |
47 | services: deepParseToJSON(JSON.parse(JSON.stringify(services))), | 44 | services: deepParseToJSON(JSON.parse(JSON.stringify(services))), |
48 | workspaces: deepParseToJSON(JSON.parse(JSON.stringify(workspaces))), | 45 | workspaces: deepParseToJSON(JSON.parse(JSON.stringify(workspaces))), |
49 | }; | 46 | } |
50 | 47 | ||
51 | return response | 48 | return response |
52 | .header('Content-Type', 'application/force-download') | 49 | .header('Content-Type', 'application/force-download') |
53 | .header('Content-disposition', 'attachment; filename=export.ferdium-data') | 50 | .header('Content-disposition', 'attachment; filename=export.ferdium-data') |
54 | .send(exportData); | 51 | .send(exportData) |
55 | } | 52 | } |
56 | } | 53 | } |
diff --git a/app/Controllers/Http/Dashboard/ForgotPasswordController.ts b/app/Controllers/Http/Dashboard/ForgotPasswordController.ts index da05bbd..f7b1d0e 100644 --- a/app/Controllers/Http/Dashboard/ForgotPasswordController.ts +++ b/app/Controllers/Http/Dashboard/ForgotPasswordController.ts | |||
@@ -1,41 +1,41 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | import { schema, rules, validator } from '@ioc:Adonis/Core/Validator'; | 2 | import { schema, rules, validator } from '@adonisjs/validator' |
3 | import User from 'App/Models/User'; | 3 | import User from '#app/Models/User' |
4 | 4 | ||
5 | export default class ForgotPasswordController { | 5 | export default class ForgotPasswordController { |
6 | /** | 6 | /** |
7 | * Display the forgot password form | 7 | * Display the forgot password form |
8 | */ | 8 | */ |
9 | public async show({ view }: HttpContextContract) { | 9 | public async show({ view }: HttpContext) { |
10 | return view.render('dashboard/forgotPassword'); | 10 | return view.render('dashboard/forgotPassword') |
11 | } | 11 | } |
12 | 12 | ||
13 | /** | 13 | /** |
14 | * Send forget password email to user | 14 | * Send forget password email to user |
15 | */ | 15 | */ |
16 | public async forgotPassword({ view, request }: HttpContextContract) { | 16 | public async forgotPassword({ view, request }: HttpContext) { |
17 | try { | 17 | try { |
18 | await validator.validate({ | 18 | await validator.validate({ |
19 | schema: schema.create({ | 19 | schema: schema.create({ |
20 | mail: schema.string([rules.email(), rules.required()]), | 20 | mail: schema.string([rules.email(), rules.required()]), |
21 | }), | 21 | }), |
22 | data: request.only(['mail']), | 22 | data: request.only(['mail']), |
23 | }); | 23 | }) |
24 | } catch { | 24 | } catch { |
25 | return view.render('others/message', { | 25 | return view.render('others/message', { |
26 | heading: 'Cannot reset your password', | 26 | heading: 'Cannot reset your password', |
27 | text: 'Please enter a valid email address', | 27 | text: 'Please enter a valid email address', |
28 | }); | 28 | }) |
29 | } | 29 | } |
30 | 30 | ||
31 | try { | 31 | try { |
32 | const user = await User.findByOrFail('email', request.input('mail')); | 32 | const user = await User.findByOrFail('email', request.input('mail')) |
33 | await user.forgotPassword(); | 33 | await user.forgotPassword() |
34 | } catch {} | 34 | } catch {} |
35 | 35 | ||
36 | return view.render('others/message', { | 36 | return view.render('others/message', { |
37 | heading: 'Reset password', | 37 | heading: 'Reset password', |
38 | text: 'If your provided E-Mail address is linked to an account, we have just sent an E-Mail to that address.', | 38 | text: 'If your provided E-Mail address is linked to an account, we have just sent an E-Mail to that address.', |
39 | }); | 39 | }) |
40 | } | 40 | } |
41 | } | 41 | } |
diff --git a/app/Controllers/Http/Dashboard/LogOutController.ts b/app/Controllers/Http/Dashboard/LogOutController.ts index 41cbd31..5d250c4 100644 --- a/app/Controllers/Http/Dashboard/LogOutController.ts +++ b/app/Controllers/Http/Dashboard/LogOutController.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | 2 | ||
3 | export default class LogOutController { | 3 | export default class LogOutController { |
4 | /** | 4 | /** |
5 | * Login a user | 5 | * Login a user |
6 | */ | 6 | */ |
7 | public async logout({ auth, response }: HttpContextContract) { | 7 | public async logout({ auth, response }: HttpContext) { |
8 | auth.logout(); | 8 | auth.logout() |
9 | 9 | ||
10 | return response.redirect('/user/login'); | 10 | return response.redirect('/user/login') |
11 | } | 11 | } |
12 | } | 12 | } |
diff --git a/app/Controllers/Http/Dashboard/LoginController.ts b/app/Controllers/Http/Dashboard/LoginController.ts index ffb9eeb..5a54448 100644 --- a/app/Controllers/Http/Dashboard/LoginController.ts +++ b/app/Controllers/Http/Dashboard/LoginController.ts | |||
@@ -1,26 +1,21 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | import { schema, rules, validator } from '@ioc:Adonis/Core/Validator'; | 2 | import { schema, rules, validator } from '@adonisjs/validator' |
3 | import User from 'App/Models/User'; | 3 | import User from '#app/Models/User' |
4 | import crypto from 'node:crypto'; | 4 | import crypto from 'node:crypto' |
5 | import { handleVerifyAndReHash } from '../../../../helpers/PasswordHash'; | 5 | import { handleVerifyAndReHash } from '../../../../helpers/PasswordHash.js' |
6 | 6 | ||
7 | export default class LoginController { | 7 | export default class LoginController { |
8 | /** | 8 | /** |
9 | * Display the login form | 9 | * Display the login form |
10 | */ | 10 | */ |
11 | public async show({ view }: HttpContextContract) { | 11 | public async show({ view }: HttpContext) { |
12 | return view.render('dashboard/login'); | 12 | return view.render('dashboard/login') |
13 | } | 13 | } |
14 | 14 | ||
15 | /** | 15 | /** |
16 | * Login a user | 16 | * Login a user |
17 | */ | 17 | */ |
18 | public async login({ | 18 | public async login({ request, response, auth, session }: HttpContext) { |
19 | request, | ||
20 | response, | ||
21 | auth, | ||
22 | session, | ||
23 | }: HttpContextContract) { | ||
24 | try { | 19 | try { |
25 | await validator.validate({ | 20 | await validator.validate({ |
26 | schema: schema.create({ | 21 | schema: schema.create({ |
@@ -28,54 +23,51 @@ export default class LoginController { | |||
28 | password: schema.string([rules.required()]), | 23 | password: schema.string([rules.required()]), |
29 | }), | 24 | }), |
30 | data: request.only(['mail', 'password']), | 25 | data: request.only(['mail', 'password']), |
31 | }); | 26 | }) |
32 | } catch { | 27 | } catch { |
33 | session.flash({ | 28 | session.flash({ |
34 | type: 'danger', | 29 | type: 'danger', |
35 | message: 'Invalid mail or password', | 30 | message: 'Invalid mail or password', |
36 | }); | 31 | }) |
37 | session.flashExcept(['password']); | 32 | session.flashExcept(['password']) |
38 | 33 | ||
39 | return response.redirect('/user/login'); | 34 | return response.redirect('/user/login') |
40 | } | 35 | } |
41 | 36 | ||
42 | try { | 37 | try { |
43 | const { mail, password } = request.all(); | 38 | const { mail, password } = request.all() |
44 | 39 | ||
45 | // Check if user with email exists | 40 | // Check if user with email exists |
46 | const user = await User.query().where('email', mail).first(); | 41 | const user = await User.query().where('email', mail).first() |
47 | if (!user?.email) { | 42 | if (!user?.email) { |
48 | throw new Error('User credentials not valid (Invalid email)'); | 43 | throw new Error('User credentials not valid (Invalid email)') |
49 | } | 44 | } |
50 | 45 | ||
51 | const hashedPassword = crypto | 46 | const hashedPassword = crypto.createHash('sha256').update(password).digest('base64') |
52 | .createHash('sha256') | ||
53 | .update(password) | ||
54 | .digest('base64'); | ||
55 | 47 | ||
56 | // Verify password | 48 | // Verify password |
57 | let isMatchedPassword = false; | 49 | let isMatchedPassword = false |
58 | try { | 50 | try { |
59 | isMatchedPassword = await handleVerifyAndReHash(user, hashedPassword); | 51 | isMatchedPassword = await handleVerifyAndReHash(user, hashedPassword) |
60 | } catch (error) { | 52 | } catch (error) { |
61 | return response.internalServerError({ message: error.message }); | 53 | return response.internalServerError({ message: error.message }) |
62 | } | 54 | } |
63 | 55 | ||
64 | if (!isMatchedPassword) { | 56 | if (!isMatchedPassword) { |
65 | throw new Error('User credentials not valid (Invalid password)'); | 57 | throw new Error('User credentials not valid (Invalid password)') |
66 | } | 58 | } |
67 | 59 | ||
68 | await auth.use('web').login(user); | 60 | await auth.use('web').login(user) |
69 | 61 | ||
70 | return response.redirect('/user/account'); | 62 | return response.redirect('/user/account') |
71 | } catch { | 63 | } catch { |
72 | session.flash({ | 64 | session.flash({ |
73 | type: 'danger', | 65 | type: 'danger', |
74 | message: 'Invalid mail or password', | 66 | message: 'Invalid mail or password', |
75 | }); | 67 | }) |
76 | session.flashExcept(['password']); | 68 | session.flashExcept(['password']) |
77 | 69 | ||
78 | return response.redirect('/user/login'); | 70 | return response.redirect('/user/login') |
79 | } | 71 | } |
80 | } | 72 | } |
81 | } | 73 | } |
diff --git a/app/Controllers/Http/Dashboard/ResetPasswordController.ts b/app/Controllers/Http/Dashboard/ResetPasswordController.ts index 0b9053f..b62b5d2 100644 --- a/app/Controllers/Http/Dashboard/ResetPasswordController.ts +++ b/app/Controllers/Http/Dashboard/ResetPasswordController.ts | |||
@@ -1,35 +1,30 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | import { schema, rules, validator } from '@ioc:Adonis/Core/Validator'; | 2 | import { schema, rules, validator } from '@adonisjs/validator' |
3 | import Token from 'App/Models/Token'; | 3 | import Token from '#app/Models/Token' |
4 | import moment from 'moment'; | 4 | import moment from 'moment' |
5 | import crypto from 'node:crypto'; | 5 | import crypto from 'node:crypto' |
6 | 6 | ||
7 | export default class ResetPasswordController { | 7 | export default class ResetPasswordController { |
8 | /** | 8 | /** |
9 | * Display the reset password form | 9 | * Display the reset password form |
10 | */ | 10 | */ |
11 | public async show({ view, request }: HttpContextContract) { | 11 | public async show({ view, request }: HttpContext) { |
12 | const { token } = request.qs(); | 12 | const { token } = request.qs() |
13 | 13 | ||
14 | if (token) { | 14 | if (token) { |
15 | return view.render('dashboard/resetPassword', { token }); | 15 | return view.render('dashboard/resetPassword', { token }) |
16 | } | 16 | } |
17 | 17 | ||
18 | return view.render('others/message', { | 18 | return view.render('others/message', { |
19 | heading: 'Invalid token', | 19 | heading: 'Invalid token', |
20 | text: 'Please make sure you are using a valid and recent link to reset your password.', | 20 | text: 'Please make sure you are using a valid and recent link to reset your password.', |
21 | }); | 21 | }) |
22 | } | 22 | } |
23 | 23 | ||
24 | /** | 24 | /** |
25 | * Resets user password | 25 | * Resets user password |
26 | */ | 26 | */ |
27 | public async resetPassword({ | 27 | public async resetPassword({ response, request, session, view }: HttpContext) { |
28 | response, | ||
29 | request, | ||
30 | session, | ||
31 | view, | ||
32 | }: HttpContextContract) { | ||
33 | try { | 28 | try { |
34 | await validator.validate({ | 29 | await validator.validate({ |
35 | schema: schema.create({ | 30 | schema: schema.create({ |
@@ -37,14 +32,14 @@ export default class ResetPasswordController { | |||
37 | token: schema.string([rules.required()]), | 32 | token: schema.string([rules.required()]), |
38 | }), | 33 | }), |
39 | data: request.only(['password', 'password_confirmation', 'token']), | 34 | data: request.only(['password', 'password_confirmation', 'token']), |
40 | }); | 35 | }) |
41 | } catch { | 36 | } catch { |
42 | session.flash({ | 37 | session.flash({ |
43 | type: 'danger', | 38 | type: 'danger', |
44 | message: 'Passwords do not match', | 39 | message: 'Passwords do not match', |
45 | }); | 40 | }) |
46 | 41 | ||
47 | return response.redirect(`/user/reset?token=${request.input('token')}`); | 42 | return response.redirect(`/user/reset?token=${request.input('token')}`) |
48 | } | 43 | } |
49 | 44 | ||
50 | const tokenRow = await Token.query() | 45 | const tokenRow = await Token.query() |
@@ -52,34 +47,30 @@ export default class ResetPasswordController { | |||
52 | .where('token', request.input('token')) | 47 | .where('token', request.input('token')) |
53 | .where('type', 'forgot_password') | 48 | .where('type', 'forgot_password') |
54 | .where('is_revoked', false) | 49 | .where('is_revoked', false) |
55 | .where( | 50 | .where('updated_at', '>=', moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss')) |
56 | 'updated_at', | 51 | .first() |
57 | '>=', | ||
58 | moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'), | ||
59 | ) | ||
60 | .first(); | ||
61 | 52 | ||
62 | if (!tokenRow) { | 53 | if (!tokenRow) { |
63 | return view.render('others/message', { | 54 | return view.render('others/message', { |
64 | heading: 'Cannot reset your password', | 55 | heading: 'Cannot reset your password', |
65 | text: 'Please make sure you are using a valid and recent link to reset your password and that your passwords entered match.', | 56 | text: 'Please make sure you are using a valid and recent link to reset your password and that your passwords entered match.', |
66 | }); | 57 | }) |
67 | } | 58 | } |
68 | 59 | ||
69 | // Update user password | 60 | // Update user password |
70 | const hashedPassword = crypto | 61 | const hashedPassword = crypto |
71 | .createHash('sha256') | 62 | .createHash('sha256') |
72 | .update(request.input('password')) | 63 | .update(request.input('password')) |
73 | .digest('base64'); | 64 | .digest('base64') |
74 | tokenRow.user.password = hashedPassword; | 65 | tokenRow.user.password = hashedPassword |
75 | await tokenRow.user.save(); | 66 | await tokenRow.user.save() |
76 | 67 | ||
77 | // Delete token to prevent it from being used again | 68 | // Delete token to prevent it from being used again |
78 | await tokenRow.delete(); | 69 | await tokenRow.delete() |
79 | 70 | ||
80 | return view.render('others/message', { | 71 | return view.render('others/message', { |
81 | heading: 'Reset password', | 72 | heading: 'Reset password', |
82 | text: 'Successfully reset your password. You can now login to your account using your new password.', | 73 | text: 'Successfully reset your password. You can now login to your account using your new password.', |
83 | }); | 74 | }) |
84 | } | 75 | } |
85 | } | 76 | } |
diff --git a/app/Controllers/Http/Dashboard/TransferController.ts b/app/Controllers/Http/Dashboard/TransferController.ts index b113e50..0296973 100644 --- a/app/Controllers/Http/Dashboard/TransferController.ts +++ b/app/Controllers/Http/Dashboard/TransferController.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | import { schema, validator } from '@ioc:Adonis/Core/Validator'; | 2 | import { schema, validator } from '@adonisjs/validator' |
3 | import Service from 'App/Models/Service'; | 3 | import Service from '#app/Models/Service' |
4 | import Workspace from 'App/Models/Workspace'; | 4 | import Workspace from '#app/Models/Workspace' |
5 | import { v4 as uuidv4 } from 'uuid'; | 5 | import { v4 as uuidv4 } from 'uuid' |
6 | 6 | ||
7 | const importSchema = schema.create({ | 7 | const importSchema = schema.create({ |
8 | username: schema.string(), | 8 | username: schema.string(), |
@@ -10,58 +10,52 @@ const importSchema = schema.create({ | |||
10 | mail: schema.string(), | 10 | mail: schema.string(), |
11 | services: schema.array().anyMembers(), | 11 | services: schema.array().anyMembers(), |
12 | workspaces: schema.array().anyMembers(), | 12 | workspaces: schema.array().anyMembers(), |
13 | }); | 13 | }) |
14 | 14 | ||
15 | export default class TransferController { | 15 | export default class TransferController { |
16 | /** | 16 | /** |
17 | * Display the transfer page | 17 | * Display the transfer page |
18 | */ | 18 | */ |
19 | public async show({ view }: HttpContextContract) { | 19 | public async show({ view }: HttpContext) { |
20 | return view.render('dashboard/transfer'); | 20 | return view.render('dashboard/transfer') |
21 | } | 21 | } |
22 | 22 | ||
23 | public async import({ | 23 | public async import({ auth, request, response, session, view }: HttpContext) { |
24 | auth, | 24 | let file |
25 | request, | ||
26 | response, | ||
27 | session, | ||
28 | view, | ||
29 | }: HttpContextContract) { | ||
30 | let file; | ||
31 | try { | 25 | try { |
32 | file = await validator.validate({ | 26 | file = await validator.validate({ |
33 | schema: importSchema, | 27 | schema: importSchema, |
34 | data: JSON.parse(request.body().file), | 28 | data: JSON.parse(request.body().file), |
35 | }); | 29 | }) |
36 | } catch { | 30 | } catch { |
37 | session.flash({ | 31 | session.flash({ |
38 | message: 'Invalid Ferdium account file', | 32 | message: 'Invalid Ferdium account file', |
39 | }); | 33 | }) |
40 | 34 | ||
41 | return response.redirect('/user/transfer'); | 35 | return response.redirect('/user/transfer') |
42 | } | 36 | } |
43 | 37 | ||
44 | if (!file?.services || !file.workspaces) { | 38 | if (!file?.services || !file.workspaces) { |
45 | session.flash({ | 39 | session.flash({ |
46 | type: 'danger', | 40 | type: 'danger', |
47 | message: 'Invalid Ferdium account file (2)', | 41 | message: 'Invalid Ferdium account file (2)', |
48 | }); | 42 | }) |
49 | return response.redirect('/user/transfer'); | 43 | return response.redirect('/user/transfer') |
50 | } | 44 | } |
51 | 45 | ||
52 | const serviceIdTranslation = {}; | 46 | const serviceIdTranslation = {} |
53 | 47 | ||
54 | // Import services | 48 | // Import services |
55 | try { | 49 | try { |
56 | for (const service of file.services) { | 50 | for (const service of file.services) { |
57 | // Get new, unused uuid | 51 | // Get new, unused uuid |
58 | let serviceId; | 52 | let serviceId |
59 | do { | 53 | do { |
60 | serviceId = uuidv4(); | 54 | serviceId = uuidv4() |
61 | } while ( | 55 | } while ( |
62 | // eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member | 56 | // eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member |
63 | (await Service.query().where('serviceId', serviceId)).length > 0 | 57 | (await Service.query().where('serviceId', serviceId)).length > 0 |
64 | ); | 58 | ) |
65 | 59 | ||
66 | // eslint-disable-next-line no-await-in-loop | 60 | // eslint-disable-next-line no-await-in-loop |
67 | await Service.create({ | 61 | await Service.create({ |
@@ -73,38 +67,37 @@ export default class TransferController { | |||
73 | typeof service.settings === 'string' | 67 | typeof service.settings === 'string' |
74 | ? service.settings | 68 | ? service.settings |
75 | : JSON.stringify(service.settings), | 69 | : JSON.stringify(service.settings), |
76 | }); | 70 | }) |
77 | 71 | ||
78 | // @ts-expect-error Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{}' | 72 | // @ts-expect-error Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{}' |
79 | serviceIdTranslation[service.service_id || service.serviceId] = | 73 | serviceIdTranslation[service.service_id || service.serviceId] = serviceId |
80 | serviceId; | ||
81 | } | 74 | } |
82 | } catch (error) { | 75 | } catch (error) { |
83 | // eslint-disable-next-line no-console | 76 | // eslint-disable-next-line no-console |
84 | console.log(error); | 77 | console.log(error) |
85 | const errorMessage = `Could not import your services into our system.\nError: ${error}`; | 78 | const errorMessage = `Could not import your services into our system.\nError: ${error}` |
86 | return view.render('others/message', { | 79 | return view.render('others/message', { |
87 | heading: 'Error while importing', | 80 | heading: 'Error while importing', |
88 | text: errorMessage, | 81 | text: errorMessage, |
89 | }); | 82 | }) |
90 | } | 83 | } |
91 | 84 | ||
92 | // Import workspaces | 85 | // Import workspaces |
93 | try { | 86 | try { |
94 | for (const workspace of file.workspaces) { | 87 | for (const workspace of file.workspaces) { |
95 | let workspaceId; | 88 | let workspaceId |
96 | 89 | ||
97 | do { | 90 | do { |
98 | workspaceId = uuidv4(); | 91 | workspaceId = uuidv4() |
99 | } while ( | 92 | } while ( |
100 | // eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member | 93 | // eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member |
101 | (await Workspace.query().where('workspaceId', workspaceId)).length > 0 | 94 | (await Workspace.query().where('workspaceId', workspaceId)).length > 0 |
102 | ); | 95 | ) |
103 | 96 | ||
104 | const services = workspace.services.map( | 97 | const services = workspace.services.map( |
105 | // @ts-expect-error Parameter 'service' implicitly has an 'any' type. | 98 | // @ts-expect-error Parameter 'service' implicitly has an 'any' type. |
106 | service => serviceIdTranslation[service], | 99 | (service) => serviceIdTranslation[service] |
107 | ); | 100 | ) |
108 | 101 | ||
109 | // eslint-disable-next-line no-await-in-loop | 102 | // eslint-disable-next-line no-await-in-loop |
110 | await Workspace.create({ | 103 | await Workspace.create({ |
@@ -114,22 +107,20 @@ export default class TransferController { | |||
114 | order: workspace.order, | 107 | order: workspace.order, |
115 | services: JSON.stringify(services), | 108 | services: JSON.stringify(services), |
116 | data: | 109 | data: |
117 | typeof workspace.data === 'string' | 110 | typeof workspace.data === 'string' ? workspace.data : JSON.stringify(workspace.data), |
118 | ? workspace.data | 111 | }) |
119 | : JSON.stringify(workspace.data), | ||
120 | }); | ||
121 | } | 112 | } |
122 | } catch (error) { | 113 | } catch (error) { |
123 | const errorMessage = `Could not import your workspaces into our system.\nError: ${error}`; | 114 | const errorMessage = `Could not import your workspaces into our system.\nError: ${error}` |
124 | return view.render('others/message', { | 115 | return view.render('others/message', { |
125 | heading: 'Error while importing', | 116 | heading: 'Error while importing', |
126 | text: errorMessage, | 117 | text: errorMessage, |
127 | }); | 118 | }) |
128 | } | 119 | } |
129 | 120 | ||
130 | return view.render('others/message', { | 121 | return view.render('others/message', { |
131 | heading: 'Successfully imported', | 122 | heading: 'Successfully imported', |
132 | text: 'Your account has been imported, you can now login as usual!', | 123 | text: 'Your account has been imported, you can now login as usual!', |
133 | }); | 124 | }) |
134 | } | 125 | } |
135 | } | 126 | } |
diff --git a/app/Controllers/Http/DashboardController.ts b/app/Controllers/Http/DashboardController.ts index a6f5b44..2a9fb13 100644 --- a/app/Controllers/Http/DashboardController.ts +++ b/app/Controllers/Http/DashboardController.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | // import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' | 1 | // import type { HttpContext } from '@adonisjs/core/http'; |
2 | 2 | ||
3 | export default class DashboardController {} | 3 | export default class DashboardController {} |
4 | 4 | ||
diff --git a/app/Controllers/Http/HealthController.ts b/app/Controllers/Http/HealthController.ts index bf185d8..59094e2 100644 --- a/app/Controllers/Http/HealthController.ts +++ b/app/Controllers/Http/HealthController.ts | |||
@@ -5,6 +5,6 @@ export default class HealthController { | |||
5 | return { | 5 | return { |
6 | api: 'success', | 6 | api: 'success', |
7 | db: 'success', | 7 | db: 'success', |
8 | }; | 8 | } |
9 | } | 9 | } |
10 | } | 10 | } |
diff --git a/app/Controllers/Http/HomeController.ts b/app/Controllers/Http/HomeController.ts index dbe9fbd..669d970 100644 --- a/app/Controllers/Http/HomeController.ts +++ b/app/Controllers/Http/HomeController.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | // import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' | 1 | // import type { HttpContext } from '@adonisjs/core/http' |
2 | 2 | ||
3 | export default class HomeController { | 3 | export default class HomeController { |
4 | public async index() { | 4 | public async index() { |
5 | // TODO: Actually do something instead of alwayas returning success. | 5 | // TODO: Actually do something instead of alwayas returning success. |
6 | 6 | ||
7 | return { hello: 'world' }; | 7 | return { hello: 'world' } |
8 | } | 8 | } |
9 | } | 9 | } |
diff --git a/app/Controllers/Http/RecipeController.ts b/app/Controllers/Http/RecipeController.ts index 5186a11..e43bcf8 100644 --- a/app/Controllers/Http/RecipeController.ts +++ b/app/Controllers/Http/RecipeController.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | import fs from 'fs-extra'; | 2 | import fs from 'fs-extra' |
3 | import Application from '@ioc:Adonis/Core/Application'; | 3 | import { app } from '@adonisjs/core/services/app' |
4 | import path from 'node:path'; | 4 | import path from 'node:path' |
5 | import Recipe from 'App/Models/Recipe'; | 5 | import Recipe from '#app/Models/Recipe' |
6 | import { isCreationEnabled } from 'Config/app'; | 6 | import { isCreationEnabled } from '#config/app' |
7 | import { validator, schema, rules } from '@ioc:Adonis/Core/Validator'; | 7 | import { validator, schema, rules } from '@adonisjs/validator' |
8 | import targz from 'targz'; | 8 | import targz from 'targz' |
9 | import semver from 'semver'; | 9 | import semver from 'semver' |
10 | import Drive from '@ioc:Adonis/Core/Drive'; | 10 | import Drive from '@ioc:Adonis/Core/Drive' |
11 | 11 | ||
12 | // TODO: This file needs to be refactored and cleaned up to include types | 12 | // TODO: This file needs to be refactored and cleaned up to include types |
13 | 13 | ||
@@ -18,17 +18,17 @@ const createSchema = schema.create({ | |||
18 | // author: 'required|accepted', | 18 | // author: 'required|accepted', |
19 | author: schema.string(), | 19 | author: schema.string(), |
20 | svg: schema.string([rules.url()]), | 20 | svg: schema.string([rules.url()]), |
21 | }); | 21 | }) |
22 | 22 | ||
23 | const searchSchema = schema.create({ | 23 | const searchSchema = schema.create({ |
24 | needle: schema.string(), | 24 | needle: schema.string(), |
25 | }); | 25 | }) |
26 | 26 | ||
27 | const downloadSchema = schema.create({ | 27 | const downloadSchema = schema.create({ |
28 | // TODO: Check if this is correct | 28 | // TODO: Check if this is correct |
29 | // recipe: 'required|accepted', | 29 | // recipe: 'required|accepted', |
30 | recipe: schema.string(), | 30 | recipe: schema.string(), |
31 | }); | 31 | }) |
32 | 32 | ||
33 | const compress = (src: string, dest: string) => | 33 | const compress = (src: string, dest: string) => |
34 | new Promise((resolve, reject) => { | 34 | new Promise((resolve, reject) => { |
@@ -37,87 +37,76 @@ const compress = (src: string, dest: string) => | |||
37 | src, | 37 | src, |
38 | dest, | 38 | dest, |
39 | }, | 39 | }, |
40 | err => { | 40 | (err) => { |
41 | if (err) { | 41 | if (err) { |
42 | reject(err); | 42 | reject(err) |
43 | } else { | 43 | } else { |
44 | resolve(dest); | 44 | resolve(dest) |
45 | } | 45 | } |
46 | }, | 46 | } |
47 | ); | 47 | ) |
48 | }); | 48 | }) |
49 | 49 | ||
50 | export default class RecipesController { | 50 | export default class RecipesController { |
51 | // List official and custom recipes | 51 | // List official and custom recipes |
52 | public async list({ response }: HttpContextContract) { | 52 | public async list({ response }: HttpContext) { |
53 | const officialRecipes = fs.readJsonSync( | 53 | const officialRecipes = fs.readJsonSync(path.join(app.appRoot, 'recipes', 'all.json')) |
54 | path.join(Application.appRoot, 'recipes', 'all.json'), | 54 | const customRecipesArray = await Recipe.all() |
55 | ); | 55 | const customRecipes = customRecipesArray.map((recipe) => ({ |
56 | const customRecipesArray = await Recipe.all(); | ||
57 | const customRecipes = customRecipesArray.map(recipe => ({ | ||
58 | id: recipe.recipeId, | 56 | id: recipe.recipeId, |
59 | name: recipe.name, | 57 | name: recipe.name, |
60 | ...(typeof recipe.data === 'string' | 58 | ...(typeof recipe.data === 'string' ? JSON.parse(recipe.data) : recipe.data), |
61 | ? JSON.parse(recipe.data) | 59 | })) |
62 | : recipe.data), | ||
63 | })); | ||
64 | 60 | ||
65 | const recipes = [...officialRecipes, ...customRecipes]; | 61 | const recipes = [...officialRecipes, ...customRecipes] |
66 | 62 | ||
67 | return response.send(recipes); | 63 | return response.send(recipes) |
68 | } | 64 | } |
69 | 65 | ||
70 | // TODO: Test this endpoint | 66 | // TODO: Test this endpoint |
71 | // Create a new recipe using the new.html page | 67 | // Create a new recipe using the new.html page |
72 | public async create({ request, response }: HttpContextContract) { | 68 | public async create({ request, response }: HttpContext) { |
73 | // Check if recipe creation is enabled | 69 | // Check if recipe creation is enabled |
74 | if (isCreationEnabled === 'false') { | 70 | if (isCreationEnabled === 'false') { |
75 | return response.send( | 71 | return response.send("This server doesn't allow the creation of new recipes.") |
76 | 'This server doesn\'t allow the creation of new recipes.', | ||
77 | ); | ||
78 | } | 72 | } |
79 | 73 | ||
80 | // Validate user input | 74 | // Validate user input |
81 | let data; | 75 | let data |
82 | try { | 76 | try { |
83 | data = await request.validate({ schema: createSchema }); | 77 | data = await request.validate({ schema: createSchema }) |
84 | } catch (error) { | 78 | } catch (error) { |
85 | return response.status(401).send({ | 79 | return response.status(401).send({ |
86 | message: 'Invalid POST arguments', | 80 | message: 'Invalid POST arguments', |
87 | messages: error.messages, | 81 | messages: error.messages, |
88 | status: 401, | 82 | status: 401, |
89 | }); | 83 | }) |
90 | } | 84 | } |
91 | 85 | ||
92 | if (!data.id) { | 86 | if (!data.id) { |
93 | return response.send('Please provide an ID'); | 87 | return response.send('Please provide an ID') |
94 | } | 88 | } |
95 | 89 | ||
96 | // Check for invalid characters | 90 | // Check for invalid characters |
97 | if (/\.+/.test(data.id) || /\/+/.test(data.id)) { | 91 | if (/\.+/.test(data.id) || /\/+/.test(data.id)) { |
98 | return response.send( | 92 | return response.send('Invalid recipe name. Your recipe name may not contain "." or "/"') |
99 | 'Invalid recipe name. Your recipe name may not contain "." or "/"', | ||
100 | ); | ||
101 | } | 93 | } |
102 | 94 | ||
103 | // Clear temporary recipe folder | 95 | // Clear temporary recipe folder |
104 | await fs.emptyDir(Application.tmpPath('recipe')); | 96 | await fs.emptyDir(app.tmpPath('recipe')) |
105 | 97 | ||
106 | // Move uploaded files to temporary path | 98 | // Move uploaded files to temporary path |
107 | const files = request.file('files'); | 99 | const files = request.file('files') |
108 | if (!files) { | 100 | if (!files) { |
109 | return response.abort('Error processsing files.'); | 101 | return response.abort('Error processsing files.') |
110 | } | 102 | } |
111 | await files.move(Application.tmpPath('recipe')); | 103 | await files.move(app.tmpPath('recipe')) |
112 | 104 | ||
113 | // Compress files to .tar.gz file | 105 | // Compress files to .tar.gz file |
114 | const source = Application.tmpPath('recipe'); | 106 | const source = app.tmpPath('recipe') |
115 | const destination = path.join( | 107 | const destination = path.join(app.appRoot, `/recipes/archives/${data.id}.tar.gz`) |
116 | Application.appRoot, | ||
117 | `/recipes/archives/${data.id}.tar.gz`, | ||
118 | ); | ||
119 | 108 | ||
120 | compress(source, destination); | 109 | compress(source, destination) |
121 | 110 | ||
122 | // Create recipe in db | 111 | // Create recipe in db |
123 | await Recipe.create({ | 112 | await Recipe.create({ |
@@ -132,123 +121,111 @@ export default class RecipesController { | |||
132 | svg: data.svg, | 121 | svg: data.svg, |
133 | }, | 122 | }, |
134 | }), | 123 | }), |
135 | }); | 124 | }) |
136 | 125 | ||
137 | return response.send('Created new recipe'); | 126 | return response.send('Created new recipe') |
138 | } | 127 | } |
139 | 128 | ||
140 | // Search official and custom recipes | 129 | // Search official and custom recipes |
141 | public async search({ request, response }: HttpContextContract) { | 130 | public async search({ request, response }: HttpContext) { |
142 | // Validate user input | 131 | // Validate user input |
143 | let data; | 132 | let data |
144 | try { | 133 | try { |
145 | data = await request.validate({ schema: searchSchema }); | 134 | data = await request.validate({ schema: searchSchema }) |
146 | } catch (error) { | 135 | } catch (error) { |
147 | return response.status(401).send({ | 136 | return response.status(401).send({ |
148 | message: 'Please provide a needle', | 137 | message: 'Please provide a needle', |
149 | messages: error.messages, | 138 | messages: error.messages, |
150 | status: 401, | 139 | status: 401, |
151 | }); | 140 | }) |
152 | } | 141 | } |
153 | 142 | ||
154 | const { needle } = data; | 143 | const { needle } = data |
155 | 144 | ||
156 | // Get results | 145 | // Get results |
157 | let results; | 146 | let results |
158 | 147 | ||
159 | if (needle === 'ferdium:custom') { | 148 | if (needle === 'ferdium:custom') { |
160 | const dbResults = await Recipe.all(); | 149 | const dbResults = await Recipe.all() |
161 | results = dbResults.map(recipe => ({ | 150 | results = dbResults.map((recipe) => ({ |
162 | id: recipe.recipeId, | 151 | id: recipe.recipeId, |
163 | name: recipe.name, | 152 | name: recipe.name, |
164 | ...(typeof recipe.data === 'string' | 153 | ...(typeof recipe.data === 'string' ? JSON.parse(recipe.data) : recipe.data), |
165 | ? JSON.parse(recipe.data) | 154 | })) |
166 | : recipe.data), | ||
167 | })); | ||
168 | } else { | 155 | } else { |
169 | const localResultsArray = await Recipe.query().where( | 156 | const localResultsArray = await Recipe.query().where('name', 'LIKE', `%${needle}%`) |
170 | 'name', | 157 | results = localResultsArray.map((recipe) => ({ |
171 | 'LIKE', | ||
172 | `%${needle}%`, | ||
173 | ); | ||
174 | results = localResultsArray.map(recipe => ({ | ||
175 | id: recipe.recipeId, | 158 | id: recipe.recipeId, |
176 | name: recipe.name, | 159 | name: recipe.name, |
177 | ...(typeof recipe.data === 'string' | 160 | ...(typeof recipe.data === 'string' ? JSON.parse(recipe.data) : recipe.data), |
178 | ? JSON.parse(recipe.data) | 161 | })) |
179 | : recipe.data), | ||
180 | })); | ||
181 | } | 162 | } |
182 | 163 | ||
183 | return response.send(results); | 164 | return response.send(results) |
184 | } | 165 | } |
185 | 166 | ||
186 | public popularRecipes({ response }: HttpContextContract) { | 167 | public popularRecipes({ response }: HttpContext) { |
187 | return response.send( | 168 | return response.send( |
188 | fs | 169 | fs |
189 | .readJsonSync(path.join(Application.appRoot, 'recipes', 'all.json')) | 170 | .readJsonSync(path.join(app.appRoot, 'recipes', 'all.json')) |
190 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | 171 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
191 | .filter((recipe: any) => recipe.featured), | 172 | .filter((recipe: any) => recipe.featured) |
192 | ); | 173 | ) |
193 | } | 174 | } |
194 | 175 | ||
195 | // TODO: test this endpoint | 176 | // TODO: test this endpoint |
196 | public update({ request, response }: HttpContextContract) { | 177 | public update({ request, response }: HttpContext) { |
197 | const updates = []; | 178 | const updates = [] |
198 | const recipes = request.all(); | 179 | const recipes = request.all() |
199 | const allJson = fs.readJsonSync( | 180 | const allJson = fs.readJsonSync(path.join(app.appRoot, 'recipes', 'all.json')) |
200 | path.join(Application.appRoot, 'recipes', 'all.json'), | ||
201 | ); | ||
202 | 181 | ||
203 | for (const recipe of Object.keys(recipes)) { | 182 | for (const recipe of Object.keys(recipes)) { |
204 | const version = recipes[recipe]; | 183 | const version = recipes[recipe] |
205 | 184 | ||
206 | // Find recipe in local recipe repository | 185 | // Find recipe in local recipe repository |
207 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | 186 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
208 | const localRecipe = allJson.find((r: any) => r.id === recipe); | 187 | const localRecipe = allJson.find((r: any) => r.id === recipe) |
209 | if (localRecipe && semver.lt(version, localRecipe.version)) { | 188 | if (localRecipe && semver.lt(version, localRecipe.version)) { |
210 | updates.push(recipe); | 189 | updates.push(recipe) |
211 | } | 190 | } |
212 | } | 191 | } |
213 | 192 | ||
214 | return response.send(updates); | 193 | return response.send(updates) |
215 | } | 194 | } |
216 | 195 | ||
217 | // TODO: test this endpoint | 196 | // TODO: test this endpoint |
218 | // Download a recipe | 197 | // Download a recipe |
219 | public async download({ response, params }: HttpContextContract) { | 198 | public async download({ response, params }: HttpContext) { |
220 | // Validate user input | 199 | // Validate user input |
221 | let data; | 200 | let data |
222 | try { | 201 | try { |
223 | data = await validator.validate({ | 202 | data = await validator.validate({ |
224 | data: params, | 203 | data: params, |
225 | schema: downloadSchema, | 204 | schema: downloadSchema, |
226 | }); | 205 | }) |
227 | } catch (error) { | 206 | } catch (error) { |
228 | return response.status(401).send({ | 207 | return response.status(401).send({ |
229 | message: 'Please provide a recipe ID', | 208 | message: 'Please provide a recipe ID', |
230 | messages: error.messages, | 209 | messages: error.messages, |
231 | status: 401, | 210 | status: 401, |
232 | }); | 211 | }) |
233 | } | 212 | } |
234 | 213 | ||
235 | const service = data.recipe; | 214 | const service = data.recipe |
236 | 215 | ||
237 | // Check for invalid characters | 216 | // Check for invalid characters |
238 | if (/\.+/.test(service) || /\/+/.test(service)) { | 217 | if (/\.+/.test(service) || /\/+/.test(service)) { |
239 | return response.send('Invalid recipe name'); | 218 | return response.send('Invalid recipe name') |
240 | } | 219 | } |
241 | 220 | ||
242 | // Check if recipe exists in recipes folder | 221 | // Check if recipe exists in recipes folder |
243 | if (await Drive.exists(`${service}.tar.gz`)) { | 222 | if (await Drive.exists(`${service}.tar.gz`)) { |
244 | return response | 223 | return response.type('.tar.gz').send(await Drive.get(`${service}.tar.gz`)) |
245 | .type('.tar.gz') | ||
246 | .send(await Drive.get(`${service}.tar.gz`)); | ||
247 | } | 224 | } |
248 | 225 | ||
249 | return response.status(400).send({ | 226 | return response.status(400).send({ |
250 | message: 'Recipe not found', | 227 | message: 'Recipe not found', |
251 | code: 'recipe-not-found', | 228 | code: 'recipe-not-found', |
252 | }); | 229 | }) |
253 | } | 230 | } |
254 | } | 231 | } |
diff --git a/app/Controllers/Http/ServiceController.ts b/app/Controllers/Http/ServiceController.ts index 76e72e4..9988244 100644 --- a/app/Controllers/Http/ServiceController.ts +++ b/app/Controllers/Http/ServiceController.ts | |||
@@ -1,49 +1,49 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | import { schema } from '@ioc:Adonis/Core/Validator'; | 2 | import { schema } from '@adonisjs/validator' |
3 | import Service from 'App/Models/Service'; | 3 | import Service from '#app/Models/Service' |
4 | import { url } from 'Config/app'; | 4 | import { url } from '#config/app' |
5 | import { v4 as uuid } from 'uuid'; | 5 | import { v4 as uuid } from 'uuid' |
6 | import * as fs from 'fs-extra'; | 6 | import * as fs from 'fs-extra' |
7 | import path from 'node:path'; | 7 | import path from 'node:path' |
8 | import Application from '@ioc:Adonis/Core/Application'; | 8 | import { app } from '@adonisjs/core/services/app' |
9 | import sanitize from 'sanitize-filename'; | 9 | import sanitize from 'sanitize-filename' |
10 | 10 | ||
11 | const createSchema = schema.create({ | 11 | const createSchema = schema.create({ |
12 | name: schema.string(), | 12 | name: schema.string(), |
13 | recipeId: schema.string(), | 13 | recipeId: schema.string(), |
14 | }); | 14 | }) |
15 | 15 | ||
16 | export default class ServiceController { | 16 | export default class ServiceController { |
17 | // Create a new service for user | 17 | // Create a new service for user |
18 | public async create({ request, response, auth }: HttpContextContract) { | 18 | public async create({ request, response, auth }: HttpContext) { |
19 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. | 19 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. |
20 | const user = auth.user ?? request.user; | 20 | const user = auth.user ?? request.user |
21 | 21 | ||
22 | if (!user) { | 22 | if (!user) { |
23 | return response.unauthorized('Missing or invalid api token'); | 23 | return response.unauthorized('Missing or invalid api token') |
24 | } | 24 | } |
25 | 25 | ||
26 | // Validate user input | 26 | // Validate user input |
27 | const data = request.all(); | 27 | const data = request.all() |
28 | 28 | ||
29 | try { | 29 | try { |
30 | await request.validate({ schema: createSchema }); | 30 | await request.validate({ schema: createSchema }) |
31 | } catch (error) { | 31 | } catch (error) { |
32 | return response.status(401).send({ | 32 | return response.status(401).send({ |
33 | message: 'Invalid POST arguments', | 33 | message: 'Invalid POST arguments', |
34 | messages: error.messages, | 34 | messages: error.messages, |
35 | status: 401, | 35 | status: 401, |
36 | }); | 36 | }) |
37 | } | 37 | } |
38 | 38 | ||
39 | // Get new, unused uuid | 39 | // Get new, unused uuid |
40 | let serviceId; | 40 | let serviceId |
41 | do { | 41 | do { |
42 | serviceId = uuid(); | 42 | serviceId = uuid() |
43 | } while ( | 43 | } while ( |
44 | // eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member | 44 | // eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member |
45 | (await Service.query().where('serviceId', serviceId)).length > 0 | 45 | (await Service.query().where('serviceId', serviceId)).length > 0 |
46 | ); | 46 | ) |
47 | 47 | ||
48 | await Service.create({ | 48 | await Service.create({ |
49 | userId: user.id, | 49 | userId: user.id, |
@@ -51,7 +51,7 @@ export default class ServiceController { | |||
51 | name: data.name, | 51 | name: data.name, |
52 | recipeId: data.recipeId, | 52 | recipeId: data.recipeId, |
53 | settings: JSON.stringify(data), | 53 | settings: JSON.stringify(data), |
54 | }); | 54 | }) |
55 | 55 | ||
56 | return response.send({ | 56 | return response.send({ |
57 | data: { | 57 | data: { |
@@ -72,28 +72,26 @@ export default class ServiceController { | |||
72 | ...data, | 72 | ...data, |
73 | }, | 73 | }, |
74 | status: ['created'], | 74 | status: ['created'], |
75 | }); | 75 | }) |
76 | } | 76 | } |
77 | 77 | ||
78 | // List all services a user has created | 78 | // List all services a user has created |
79 | public async list({ request, response, auth }: HttpContextContract) { | 79 | public async list({ request, response, auth }: HttpContext) { |
80 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. | 80 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. |
81 | const user = auth.user ?? request.user; | 81 | const user = auth.user ?? request.user |
82 | 82 | ||
83 | if (!user) { | 83 | if (!user) { |
84 | return response.unauthorized('Missing or invalid api token'); | 84 | return response.unauthorized('Missing or invalid api token') |
85 | } | 85 | } |
86 | 86 | ||
87 | const { id } = user; | 87 | const { id } = user |
88 | const services = await user.related('services').query(); | 88 | const services = await user.related('services').query() |
89 | 89 | ||
90 | // Convert to array with all data Franz wants | 90 | // Convert to array with all data Franz wants |
91 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | 91 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
92 | const servicesArray = services.map((service: any) => { | 92 | const servicesArray = services.map((service: any) => { |
93 | const settings = | 93 | const settings = |
94 | typeof service.settings === 'string' | 94 | typeof service.settings === 'string' ? JSON.parse(service.settings) : service.settings |
95 | ? JSON.parse(service.settings) | ||
96 | : service.settings; | ||
97 | 95 | ||
98 | return { | 96 | return { |
99 | customRecipe: false, | 97 | customRecipe: false, |
@@ -110,99 +108,87 @@ export default class ServiceController { | |||
110 | iconUrl: settings.iconId | 108 | iconUrl: settings.iconId |
111 | ? `${url}/v1/icon/${settings.iconId}` | 109 | ? `${url}/v1/icon/${settings.iconId}` |
112 | : // eslint-disable-next-line unicorn/no-null | 110 | : // eslint-disable-next-line unicorn/no-null |
113 | null, | 111 | null, |
114 | id: service.serviceId, | 112 | id: service.serviceId, |
115 | name: service.name, | 113 | name: service.name, |
116 | recipeId: service.recipeId, | 114 | recipeId: service.recipeId, |
117 | userId: id, | 115 | userId: id, |
118 | }; | 116 | } |
119 | }); | 117 | }) |
120 | 118 | ||
121 | return response.send(servicesArray); | 119 | return response.send(servicesArray) |
122 | } | 120 | } |
123 | 121 | ||
124 | public async delete({ | 122 | public async delete({ request, params, auth, response }: HttpContext) { |
125 | request, | ||
126 | params, | ||
127 | auth, | ||
128 | response, | ||
129 | }: HttpContextContract) { | ||
130 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. | 123 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. |
131 | const user = auth.user ?? request.user; | 124 | const user = auth.user ?? request.user |
132 | 125 | ||
133 | if (!user) { | 126 | if (!user) { |
134 | return response.unauthorized('Missing or invalid api token'); | 127 | return response.unauthorized('Missing or invalid api token') |
135 | } | 128 | } |
136 | 129 | ||
137 | // Update data in database | 130 | // Update data in database |
138 | await Service.query() | 131 | await Service.query().where('serviceId', params.id).where('userId', user.id).delete() |
139 | .where('serviceId', params.id) | ||
140 | .where('userId', user.id) | ||
141 | .delete(); | ||
142 | 132 | ||
143 | return response.send({ | 133 | return response.send({ |
144 | message: 'Sucessfully deleted service', | 134 | message: 'Sucessfully deleted service', |
145 | status: 200, | 135 | status: 200, |
146 | }); | 136 | }) |
147 | } | 137 | } |
148 | 138 | ||
149 | // TODO: Test if icon upload works | 139 | // TODO: Test if icon upload works |
150 | public async edit({ request, response, auth, params }: HttpContextContract) { | 140 | public async edit({ request, response, auth, params }: HttpContext) { |
151 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. | 141 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. |
152 | const user = auth.user ?? request.user; | 142 | const user = auth.user ?? request.user |
153 | 143 | ||
154 | if (!user) { | 144 | if (!user) { |
155 | return response.unauthorized('Missing or invalid api token'); | 145 | return response.unauthorized('Missing or invalid api token') |
156 | } | 146 | } |
157 | 147 | ||
158 | const { id } = params; | 148 | const { id } = params |
159 | const service = await Service.query() | 149 | const service = await Service.query() |
160 | .where('serviceId', id) | 150 | .where('serviceId', id) |
161 | .where('userId', user.id) | 151 | .where('userId', user.id) |
162 | .firstOrFail(); | 152 | .firstOrFail() |
163 | 153 | ||
164 | if (request.file('icon')) { | 154 | if (request.file('icon')) { |
165 | // Upload custom service icon | 155 | // Upload custom service icon |
166 | const icon = request.file('icon', { | 156 | const icon = request.file('icon', { |
167 | extnames: ['png', 'jpg', 'jpeg', 'svg'], | 157 | extnames: ['png', 'jpg', 'jpeg', 'svg'], |
168 | size: '2mb', | 158 | size: '2mb', |
169 | }); | 159 | }) |
170 | 160 | ||
171 | if (icon === null) { | 161 | if (icon === null) { |
172 | return response.badRequest('Icon not uploaded.'); | 162 | return response.badRequest('Icon not uploaded.') |
173 | } | 163 | } |
174 | 164 | ||
175 | const settings = | 165 | const settings = |
176 | typeof service.settings === 'string' | 166 | typeof service.settings === 'string' ? JSON.parse(service.settings) : service.settings |
177 | ? JSON.parse(service.settings) | ||
178 | : service.settings; | ||
179 | 167 | ||
180 | let iconId; | 168 | let iconId |
181 | do { | 169 | do { |
182 | iconId = uuid() + uuid(); | 170 | iconId = uuid() + uuid() |
183 | } while ( | 171 | } while ( |
184 | // eslint-disable-next-line no-await-in-loop | 172 | // eslint-disable-next-line no-await-in-loop |
185 | await fs.exists(path.join(Application.tmpPath('uploads'), iconId)) | 173 | await fs.exists(path.join(app.tmpPath('uploads'), iconId)) |
186 | ); | 174 | ) |
187 | iconId = `${iconId}.${icon.extname}`; | 175 | iconId = `${iconId}.${icon.extname}` |
188 | 176 | ||
189 | await icon.move(Application.tmpPath('uploads'), { | 177 | await icon.move(app.tmpPath('uploads'), { |
190 | name: iconId, | 178 | name: iconId, |
191 | overwrite: true, | 179 | overwrite: true, |
192 | }); | 180 | }) |
193 | 181 | ||
194 | if (icon.state !== 'moved') { | 182 | if (icon.state !== 'moved') { |
195 | return response.status(500).send(icon.errors); | 183 | return response.status(500).send(icon.errors) |
196 | } | 184 | } |
197 | 185 | ||
198 | const newSettings = { | 186 | const newSettings = { |
199 | ...settings, | 187 | ...settings, |
200 | 188 | ||
201 | iconId, | 189 | iconId, |
202 | customIconVersion: settings?.customIconVersion | 190 | customIconVersion: settings?.customIconVersion ? settings.customIconVersion + 1 : 1, |
203 | ? settings.customIconVersion + 1 | 191 | } |
204 | : 1, | ||
205 | }; | ||
206 | 192 | ||
207 | // Update data in database | 193 | // Update data in database |
208 | await Service.query() | 194 | await Service.query() |
@@ -211,7 +197,7 @@ export default class ServiceController { | |||
211 | .update({ | 197 | .update({ |
212 | name: service.name, | 198 | name: service.name, |
213 | settings: JSON.stringify(newSettings), | 199 | settings: JSON.stringify(newSettings), |
214 | }); | 200 | }) |
215 | 201 | ||
216 | return response.send({ | 202 | return response.send({ |
217 | data: { | 203 | data: { |
@@ -222,28 +208,24 @@ export default class ServiceController { | |||
222 | userId: user.id, | 208 | userId: user.id, |
223 | }, | 209 | }, |
224 | status: ['updated'], | 210 | status: ['updated'], |
225 | }); | 211 | }) |
226 | } | 212 | } |
227 | // Update service info | 213 | // Update service info |
228 | const data = request.all(); | 214 | const data = request.all() |
229 | 215 | ||
230 | const settings = { | 216 | const settings = { |
231 | ...(typeof service.settings === 'string' | 217 | ...(typeof service.settings === 'string' ? JSON.parse(service.settings) : service.settings), |
232 | ? JSON.parse(service.settings) | ||
233 | : service.settings), | ||
234 | ...data, | 218 | ...data, |
235 | }; | 219 | } |
236 | 220 | ||
237 | if (settings.customIcon === 'delete') { | 221 | if (settings.customIcon === 'delete') { |
238 | fs.remove( | 222 | fs.remove(path.join(app.tmpPath('uploads'), settings.iconId)).catch((error) => { |
239 | path.join(Application.tmpPath('uploads'), settings.iconId), | 223 | console.error(error) |
240 | ).catch(error => { | 224 | }) |
241 | console.error(error); | 225 | |
242 | }); | 226 | settings.iconId = undefined |
243 | 227 | settings.customIconVersion = undefined | |
244 | settings.iconId = undefined; | 228 | settings.customIcon = '' |
245 | settings.customIconVersion = undefined; | ||
246 | settings.customIcon = ''; | ||
247 | } | 229 | } |
248 | 230 | ||
249 | // Update data in database | 231 | // Update data in database |
@@ -253,13 +235,13 @@ export default class ServiceController { | |||
253 | .update({ | 235 | .update({ |
254 | name: data.name, | 236 | name: data.name, |
255 | settings: JSON.stringify(settings), | 237 | settings: JSON.stringify(settings), |
256 | }); | 238 | }) |
257 | 239 | ||
258 | // Get updated row | 240 | // Get updated row |
259 | const serviceUpdated = await Service.query() | 241 | const serviceUpdated = await Service.query() |
260 | .where('serviceId', id) | 242 | .where('serviceId', id) |
261 | .where('userId', user.id) | 243 | .where('userId', user.id) |
262 | .firstOrFail(); | 244 | .firstOrFail() |
263 | 245 | ||
264 | return response.send({ | 246 | return response.send({ |
265 | data: { | 247 | data: { |
@@ -270,19 +252,19 @@ export default class ServiceController { | |||
270 | userId: user.id, | 252 | userId: user.id, |
271 | }, | 253 | }, |
272 | status: ['updated'], | 254 | status: ['updated'], |
273 | }); | 255 | }) |
274 | } | 256 | } |
275 | 257 | ||
276 | // TODO: Test if this works | 258 | // TODO: Test if this works |
277 | public async reorder({ request, response, auth }: HttpContextContract) { | 259 | public async reorder({ request, response, auth }: HttpContext) { |
278 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. | 260 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. |
279 | const user = auth.user ?? request.user; | 261 | const user = auth.user ?? request.user |
280 | 262 | ||
281 | if (!user) { | 263 | if (!user) { |
282 | return response.unauthorized('Missing or invalid api token'); | 264 | return response.unauthorized('Missing or invalid api token') |
283 | } | 265 | } |
284 | 266 | ||
285 | const data = request.all(); | 267 | const data = request.all() |
286 | 268 | ||
287 | for (const service of Object.keys(data)) { | 269 | for (const service of Object.keys(data)) { |
288 | // Get current settings from db | 270 | // Get current settings from db |
@@ -290,14 +272,14 @@ export default class ServiceController { | |||
290 | .where('serviceId', service) | 272 | .where('serviceId', service) |
291 | .where('userId', user.id) | 273 | .where('userId', user.id) |
292 | 274 | ||
293 | .firstOrFail(); | 275 | .firstOrFail() |
294 | 276 | ||
295 | const settings = { | 277 | const settings = { |
296 | ...(typeof serviceData.settings === 'string' | 278 | ...(typeof serviceData.settings === 'string' |
297 | ? JSON.parse(serviceData.settings) | 279 | ? JSON.parse(serviceData.settings) |
298 | : serviceData.settings), | 280 | : serviceData.settings), |
299 | order: data[service], | 281 | order: data[service], |
300 | }; | 282 | } |
301 | 283 | ||
302 | // Update data in database | 284 | // Update data in database |
303 | await Service.query() // eslint-disable-line no-await-in-loop | 285 | await Service.query() // eslint-disable-line no-await-in-loop |
@@ -305,18 +287,16 @@ export default class ServiceController { | |||
305 | .where('userId', user.id) | 287 | .where('userId', user.id) |
306 | .update({ | 288 | .update({ |
307 | settings: JSON.stringify(settings), | 289 | settings: JSON.stringify(settings), |
308 | }); | 290 | }) |
309 | } | 291 | } |
310 | 292 | ||
311 | // Get new services | 293 | // Get new services |
312 | const services = await user.related('services').query(); | 294 | const services = await user.related('services').query() |
313 | // Convert to array with all data Franz wants | 295 | // Convert to array with all data Franz wants |
314 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | 296 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
315 | const servicesArray = services.map((service: any) => { | 297 | const servicesArray = services.map((service: any) => { |
316 | const settings = | 298 | const settings = |
317 | typeof service.settings === 'string' | 299 | typeof service.settings === 'string' ? JSON.parse(service.settings) : service.settings |
318 | ? JSON.parse(service.settings) | ||
319 | : service.settings; | ||
320 | 300 | ||
321 | return { | 301 | return { |
322 | customRecipe: false, | 302 | customRecipe: false, |
@@ -333,39 +313,39 @@ export default class ServiceController { | |||
333 | iconUrl: settings.iconId | 313 | iconUrl: settings.iconId |
334 | ? `${url}/v1/icon/${settings.iconId}` | 314 | ? `${url}/v1/icon/${settings.iconId}` |
335 | : // eslint-disable-next-line unicorn/no-null | 315 | : // eslint-disable-next-line unicorn/no-null |
336 | null, | 316 | null, |
337 | id: service.serviceId, | 317 | id: service.serviceId, |
338 | name: service.name, | 318 | name: service.name, |
339 | recipeId: service.recipeId, | 319 | recipeId: service.recipeId, |
340 | userId: user.id, | 320 | userId: user.id, |
341 | }; | 321 | } |
342 | }); | 322 | }) |
343 | 323 | ||
344 | return response.send(servicesArray); | 324 | return response.send(servicesArray) |
345 | } | 325 | } |
346 | 326 | ||
347 | // TODO: Test if this works | 327 | // TODO: Test if this works |
348 | public async icon({ params, response }: HttpContextContract) { | 328 | public async icon({ params, response }: HttpContext) { |
349 | let { id } = params; | 329 | let { id } = params |
350 | 330 | ||
351 | id = sanitize(id); | 331 | id = sanitize(id) |
352 | if (id === '') { | 332 | if (id === '') { |
353 | return response.status(404).send({ | 333 | return response.status(404).send({ |
354 | status: 'Icon doesn\'t exist', | 334 | status: "Icon doesn't exist", |
355 | }); | 335 | }) |
356 | } | 336 | } |
357 | 337 | ||
358 | const iconPath = path.join(Application.tmpPath('uploads'), id); | 338 | const iconPath = path.join(app.tmpPath('uploads'), id) |
359 | 339 | ||
360 | try { | 340 | try { |
361 | await fs.access(iconPath); | 341 | await fs.access(iconPath) |
362 | } catch { | 342 | } catch { |
363 | // File not available. | 343 | // File not available. |
364 | return response.status(404).send({ | 344 | return response.status(404).send({ |
365 | status: 'Icon doesn\'t exist', | 345 | status: "Icon doesn't exist", |
366 | }); | 346 | }) |
367 | } | 347 | } |
368 | 348 | ||
369 | return response.download(iconPath); | 349 | return response.download(iconPath) |
370 | } | 350 | } |
371 | } | 351 | } |
diff --git a/app/Controllers/Http/StaticsController.ts b/app/Controllers/Http/StaticsController.ts index e221177..a94a9ba 100644 --- a/app/Controllers/Http/StaticsController.ts +++ b/app/Controllers/Http/StaticsController.ts | |||
@@ -1,3 +1,3 @@ | |||
1 | // import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' | 1 | // import type { HttpContext } from '@adonisjs/core/http' |
2 | 2 | ||
3 | export default class StaticsController {} | 3 | export default class StaticsController {} |
diff --git a/app/Controllers/Http/UserController.ts b/app/Controllers/Http/UserController.ts index ef7cfdd..088f7b1 100644 --- a/app/Controllers/Http/UserController.ts +++ b/app/Controllers/Http/UserController.ts | |||
@@ -1,134 +1,125 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | import { schema, rules } from '@ioc:Adonis/Core/Validator'; | 2 | import { schema, rules } from '@adonisjs/validator' |
3 | import User from 'App/Models/User'; | 3 | import User from '#app/Models/User' |
4 | import { connectWithFranz, isRegistrationEnabled } from '../../../config/app'; | 4 | import { connectWithFranz, isRegistrationEnabled } from '../../../config/app.js' |
5 | import crypto from 'node:crypto'; | 5 | import crypto from 'node:crypto' |
6 | import { v4 as uuid } from 'uuid'; | 6 | import { v4 as uuid } from 'uuid' |
7 | import Workspace from 'App/Models/Workspace'; | 7 | import Workspace from '#app/Models/Workspace' |
8 | import Service from 'App/Models/Service'; | 8 | import Service from '#app/Models/Service' |
9 | import fetch from 'node-fetch'; | ||
10 | 9 | ||
11 | // TODO: This file needs to be refactored and cleaned up to include types | 10 | // TODO: This file needs to be refactored and cleaned up to include types |
12 | import { handleVerifyAndReHash } from '../../../helpers/PasswordHash'; | 11 | import { handleVerifyAndReHash } from '../../../helpers/PasswordHash.js' |
13 | 12 | ||
14 | const newPostSchema = schema.create({ | 13 | const newPostSchema = schema.create({ |
15 | firstname: schema.string(), | 14 | firstname: schema.string(), |
16 | lastname: schema.string(), | 15 | lastname: schema.string(), |
17 | email: schema.string([ | 16 | email: schema.string([rules.email(), rules.unique({ table: 'users', column: 'email' })]), |
18 | rules.email(), | ||
19 | rules.unique({ table: 'users', column: 'email' }), | ||
20 | ]), | ||
21 | password: schema.string([rules.minLength(8)]), | 17 | password: schema.string([rules.minLength(8)]), |
22 | }); | 18 | }) |
23 | 19 | ||
24 | const franzImportSchema = schema.create({ | 20 | const franzImportSchema = schema.create({ |
25 | email: schema.string([ | 21 | email: schema.string([rules.email(), rules.unique({ table: 'users', column: 'email' })]), |
26 | rules.email(), | ||
27 | rules.unique({ table: 'users', column: 'email' }), | ||
28 | ]), | ||
29 | password: schema.string([rules.minLength(8)]), | 22 | password: schema.string([rules.minLength(8)]), |
30 | }); | 23 | }) |
31 | 24 | ||
32 | // // TODO: This whole controller needs to be changed such that it can support importing from both Franz and Ferdi | 25 | // // TODO: This whole controller needs to be changed such that it can support importing from both Franz and Ferdi |
33 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | 26 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
34 | const franzRequest = (route: any, method: any, auth: any) => | 27 | const franzRequest = (route: any, method: any, auth: any) => |
35 | new Promise((resolve, reject) => { | 28 | new Promise((resolve, reject) => { |
36 | const base = 'https://api.franzinfra.com/v1/'; | 29 | const base = 'https://api.franzinfra.com/v1/' |
37 | const user = | 30 | const user = |
38 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Franz/5.3.0-beta.1 Chrome/69.0.3497.128 Electron/4.2.4 Safari/537.36'; | 31 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Franz/5.3.0-beta.1 Chrome/69.0.3497.128 Electron/4.2.4 Safari/537.36' |
39 | 32 | ||
40 | try { | 33 | try { |
41 | fetch(base + route, { | 34 | fetch(base + route, { |
42 | method, | 35 | method, |
43 | headers: { | 36 | headers: { |
44 | Authorization: `Bearer ${auth}`, | 37 | 'Authorization': `Bearer ${auth}`, |
45 | 'User-Agent': user, | 38 | 'User-Agent': user, |
46 | }, | 39 | }, |
47 | }) | 40 | }) |
48 | .then(data => data.json()) | 41 | .then((data) => data.json()) |
49 | .then(json => resolve(json)); | 42 | .then((json) => resolve(json)) |
50 | } catch { | 43 | } catch { |
51 | reject(); | 44 | reject() |
52 | } | 45 | } |
53 | }); | 46 | }) |
54 | 47 | ||
55 | export default class UsersController { | 48 | export default class UsersController { |
56 | // Register a new user | 49 | // Register a new user |
57 | public async signup({ request, response, auth }: HttpContextContract) { | 50 | public async signup({ request, response, auth }: HttpContext) { |
58 | if (isRegistrationEnabled === 'false') { | 51 | if (isRegistrationEnabled === 'false') { |
59 | return response.status(401).send({ | 52 | return response.status(401).send({ |
60 | message: 'Registration is disabled on this server', | 53 | message: 'Registration is disabled on this server', |
61 | status: 401, | 54 | status: 401, |
62 | }); | 55 | }) |
63 | } | 56 | } |
64 | 57 | ||
65 | // Validate user input | 58 | // Validate user input |
66 | let data; | 59 | let data |
67 | try { | 60 | try { |
68 | data = await request.validate({ schema: newPostSchema }); | 61 | data = await request.validate({ schema: newPostSchema }) |
69 | } catch (error) { | 62 | } catch (error) { |
70 | return response.status(401).send({ | 63 | return response.status(401).send({ |
71 | message: 'Invalid POST arguments', | 64 | message: 'Invalid POST arguments', |
72 | messages: error.messages, | 65 | messages: error.messages, |
73 | status: 401, | 66 | status: 401, |
74 | }); | 67 | }) |
75 | } | 68 | } |
76 | 69 | ||
77 | // Create user in DB | 70 | // Create user in DB |
78 | let user; | 71 | let user |
79 | try { | 72 | try { |
80 | user = await User.create({ | 73 | user = await User.create({ |
81 | email: data.email, | 74 | email: data.email, |
82 | password: data.password, | 75 | password: data.password, |
83 | username: data.firstname, | 76 | username: data.firstname, |
84 | lastname: data.lastname, | 77 | lastname: data.lastname, |
85 | }); | 78 | }) |
86 | } catch { | 79 | } catch { |
87 | return response.status(401).send({ | 80 | return response.status(401).send({ |
88 | message: 'E-Mail address already in use', | 81 | message: 'E-Mail address already in use', |
89 | status: 401, | 82 | status: 401, |
90 | }); | 83 | }) |
91 | } | 84 | } |
92 | 85 | ||
93 | // Generate new auth token | 86 | // Generate new auth token |
94 | const token = await auth.use('jwt').login(user, { payload: {} }); | 87 | const token = await auth.use('jwt').login(user, { payload: {} }) |
95 | 88 | ||
96 | return response.send({ | 89 | return response.send({ |
97 | message: 'Successfully created account', | 90 | message: 'Successfully created account', |
98 | token: token.accessToken, | 91 | token: token.accessToken, |
99 | }); | 92 | }) |
100 | } | 93 | } |
101 | 94 | ||
102 | // Login using an existing user | 95 | // Login using an existing user |
103 | public async login({ request, response, auth }: HttpContextContract) { | 96 | public async login({ request, response, auth }: HttpContext) { |
104 | if (!request.header('Authorization')) { | 97 | if (!request.header('Authorization')) { |
105 | return response.status(401).send({ | 98 | return response.status(401).send({ |
106 | message: 'Please provide authorization', | 99 | message: 'Please provide authorization', |
107 | status: 401, | 100 | status: 401, |
108 | }); | 101 | }) |
109 | } | 102 | } |
110 | 103 | ||
111 | // Get auth data from auth token | 104 | // Get auth data from auth token |
112 | const authHeader = atob( | 105 | const authHeader = atob(request.header('Authorization')!.replace('Basic ', '')).split(':') |
113 | request.header('Authorization')!.replace('Basic ', ''), | ||
114 | ).split(':'); | ||
115 | 106 | ||
116 | // Check if user with email exists | 107 | // Check if user with email exists |
117 | const user = await User.query().where('email', authHeader[0]).first(); | 108 | const user = await User.query().where('email', authHeader[0]).first() |
118 | if (!user?.email) { | 109 | if (!user?.email) { |
119 | return response.status(401).send({ | 110 | return response.status(401).send({ |
120 | message: 'User credentials not valid', | 111 | message: 'User credentials not valid', |
121 | code: 'invalid-credentials', | 112 | code: 'invalid-credentials', |
122 | status: 401, | 113 | status: 401, |
123 | }); | 114 | }) |
124 | } | 115 | } |
125 | 116 | ||
126 | // Verify password | 117 | // Verify password |
127 | let isMatchedPassword = false; | 118 | let isMatchedPassword = false |
128 | try { | 119 | try { |
129 | isMatchedPassword = await handleVerifyAndReHash(user, authHeader[1]); | 120 | isMatchedPassword = await handleVerifyAndReHash(user, authHeader[1]) |
130 | } catch (error) { | 121 | } catch (error) { |
131 | return response.internalServerError({ message: error.message }); | 122 | return response.internalServerError({ message: error.message }) |
132 | } | 123 | } |
133 | 124 | ||
134 | if (!isMatchedPassword) { | 125 | if (!isMatchedPassword) { |
@@ -136,31 +127,28 @@ export default class UsersController { | |||
136 | message: 'User credentials not valid', | 127 | message: 'User credentials not valid', |
137 | code: 'invalid-credentials', | 128 | code: 'invalid-credentials', |
138 | status: 401, | 129 | status: 401, |
139 | }); | 130 | }) |
140 | } | 131 | } |
141 | 132 | ||
142 | // Generate token | 133 | // Generate token |
143 | const token = await auth.use('jwt').login(user, { payload: {} }); | 134 | const token = await auth.use('jwt').login(user, { payload: {} }) |
144 | 135 | ||
145 | return response.send({ | 136 | return response.send({ |
146 | message: 'Successfully logged in', | 137 | message: 'Successfully logged in', |
147 | token: token.accessToken, | 138 | token: token.accessToken, |
148 | }); | 139 | }) |
149 | } | 140 | } |
150 | 141 | ||
151 | // Return information about the current user | 142 | // Return information about the current user |
152 | public async me({ request, response, auth }: HttpContextContract) { | 143 | public async me({ request, response, auth }: HttpContext) { |
153 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. | 144 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. |
154 | const user = auth.user ?? request.user; | 145 | const user = auth.user ?? request.user |
155 | 146 | ||
156 | if (!user) { | 147 | if (!user) { |
157 | return response.send('Missing or invalid api token'); | 148 | return response.send('Missing or invalid api token') |
158 | } | 149 | } |
159 | 150 | ||
160 | const settings = | 151 | const settings = typeof user.settings === 'string' ? JSON.parse(user.settings) : user.settings |
161 | typeof user.settings === 'string' | ||
162 | ? JSON.parse(user.settings) | ||
163 | : user.settings; | ||
164 | 152 | ||
165 | return response.send({ | 153 | return response.send({ |
166 | accountType: 'individual', | 154 | accountType: 'individual', |
@@ -176,29 +164,29 @@ export default class UsersController { | |||
176 | lastname: user.lastname, | 164 | lastname: user.lastname, |
177 | locale: 'en-US', | 165 | locale: 'en-US', |
178 | ...settings, | 166 | ...settings, |
179 | }); | 167 | }) |
180 | } | 168 | } |
181 | 169 | ||
182 | public async updateMe({ request, response, auth }: HttpContextContract) { | 170 | public async updateMe({ request, response, auth }: HttpContext) { |
183 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. | 171 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. |
184 | const user = auth.user ?? request.user; | 172 | const user = auth.user ?? request.user |
185 | 173 | ||
186 | if (!user) { | 174 | if (!user) { |
187 | return response.send('Missing or invalid api token'); | 175 | return response.send('Missing or invalid api token') |
188 | } | 176 | } |
189 | 177 | ||
190 | let settings = user.settings || {}; | 178 | let settings = user.settings || {} |
191 | if (typeof settings === 'string') { | 179 | if (typeof settings === 'string') { |
192 | settings = JSON.parse(settings); | 180 | settings = JSON.parse(settings) |
193 | } | 181 | } |
194 | 182 | ||
195 | const newSettings = { | 183 | const newSettings = { |
196 | ...settings, | 184 | ...settings, |
197 | ...request.all(), | 185 | ...request.all(), |
198 | }; | 186 | } |
199 | 187 | ||
200 | user.settings = JSON.stringify(newSettings); | 188 | user.settings = JSON.stringify(newSettings) |
201 | await user.save(); | 189 | await user.save() |
202 | 190 | ||
203 | return response.send({ | 191 | return response.send({ |
204 | data: { | 192 | data: { |
@@ -217,140 +205,137 @@ export default class UsersController { | |||
217 | ...newSettings, | 205 | ...newSettings, |
218 | }, | 206 | }, |
219 | status: ['data-updated'], | 207 | status: ['data-updated'], |
220 | }); | 208 | }) |
221 | } | 209 | } |
222 | 210 | ||
223 | public async newToken({ request, response, auth }: HttpContextContract) { | 211 | public async newToken({ request, response, auth }: HttpContext) { |
224 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. | 212 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. |
225 | const user = auth.user ?? request.user; | 213 | const user = auth.user ?? request.user |
226 | 214 | ||
227 | if (!user) { | 215 | if (!user) { |
228 | return response.send('Missing or invalid api token'); | 216 | return response.send('Missing or invalid api token') |
229 | } | 217 | } |
230 | 218 | ||
231 | const token = await auth.use('jwt').generate(user, { payload: {} }); | 219 | const token = await auth.use('jwt').generate(user, { payload: {} }) |
232 | 220 | ||
233 | return response.send({ | 221 | return response.send({ |
234 | token: token.accessToken, | 222 | token: token.accessToken, |
235 | }); | 223 | }) |
236 | } | 224 | } |
237 | 225 | ||
238 | public async import({ request, response, view }: HttpContextContract) { | 226 | public async import({ request, response, view }: HttpContext) { |
239 | if (isRegistrationEnabled === 'false') { | 227 | if (isRegistrationEnabled === 'false') { |
240 | return response.status(401).send({ | 228 | return response.status(401).send({ |
241 | message: 'Registration is disabled on this server', | 229 | message: 'Registration is disabled on this server', |
242 | status: 401, | 230 | status: 401, |
243 | }); | 231 | }) |
244 | } | 232 | } |
245 | 233 | ||
246 | if (connectWithFranz === 'false') { | 234 | if (connectWithFranz === 'false') { |
247 | return response.send( | 235 | return response.send( |
248 | 'We could not import your Franz account data.\n\nIf you are the server owner, please set CONNECT_WITH_FRANZ to true to enable account imports.', | 236 | 'We could not import your Franz account data.\n\nIf you are the server owner, please set CONNECT_WITH_FRANZ to true to enable account imports.' |
249 | ); | 237 | ) |
250 | } | 238 | } |
251 | 239 | ||
252 | // Validate user input | 240 | // Validate user input |
253 | let data; | 241 | let data |
254 | try { | 242 | try { |
255 | data = await request.validate({ schema: franzImportSchema }); | 243 | data = await request.validate({ schema: franzImportSchema }) |
256 | } catch (error) { | 244 | } catch (error) { |
257 | return view.render('others.message', { | 245 | return view.render('others.message', { |
258 | heading: 'Error while importing', | 246 | heading: 'Error while importing', |
259 | text: error.messages, | 247 | text: error.messages, |
260 | }); | 248 | }) |
261 | } | 249 | } |
262 | 250 | ||
263 | const { email, password } = data; | 251 | const { email, password } = data |
264 | 252 | ||
265 | const hashedPassword = crypto | 253 | const hashedPassword = crypto.createHash('sha256').update(password).digest('base64') |
266 | .createHash('sha256') | ||
267 | .update(password) | ||
268 | .digest('base64'); | ||
269 | 254 | ||
270 | const base = 'https://api.franzinfra.com/v1/'; | 255 | const base = 'https://api.franzinfra.com/v1/' |
271 | const userAgent = | 256 | const userAgent = |
272 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Franz/5.3.0-beta.1 Chrome/69.0.3497.128 Electron/4.2.4 Safari/537.36'; | 257 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Franz/5.3.0-beta.1 Chrome/69.0.3497.128 Electron/4.2.4 Safari/537.36' |
273 | 258 | ||
274 | // Try to get an authentication token | 259 | // Try to get an authentication token |
275 | let token; | 260 | let token |
276 | try { | 261 | try { |
277 | const basicToken = btoa(`${email}:${hashedPassword}`); | 262 | const basicToken = btoa(`${email}:${hashedPassword}`) |
278 | const loginBody = { | 263 | const loginBody = { |
279 | isZendeskLogin: false, | 264 | isZendeskLogin: false, |
280 | }; | 265 | } |
281 | 266 | ||
282 | const rawResponse = await fetch(`${base}auth/login`, { | 267 | const rawResponse = await fetch(`${base}auth/login`, { |
283 | method: 'POST', | 268 | method: 'POST', |
284 | body: JSON.stringify(loginBody), | 269 | body: JSON.stringify(loginBody), |
285 | headers: { | 270 | headers: { |
286 | Authorization: `Basic ${basicToken}`, | 271 | 'Authorization': `Basic ${basicToken}`, |
287 | 'User-Agent': userAgent, | 272 | 'User-Agent': userAgent, |
288 | 'Content-Type': 'application/json', | 273 | 'Content-Type': 'application/json', |
289 | accept: '*/*', | 274 | 'accept': '*/*', |
290 | 'x-franz-source': 'Web', | 275 | 'x-franz-source': 'Web', |
291 | }, | 276 | }, |
292 | }); | 277 | }) |
293 | const content = await rawResponse.json(); | 278 | const content = await rawResponse.json() |
294 | 279 | ||
295 | if (!content.message || content.message !== 'Successfully logged in') { | 280 | if (!content.message || content.message !== 'Successfully logged in') { |
296 | const errorMessage = | 281 | const errorMessage = |
297 | 'Could not login into Franz with your supplied credentials. Please check and try again'; | 282 | 'Could not login into Franz with your supplied credentials. Please check and try again' |
298 | return response.status(401).send(errorMessage); | 283 | return response.status(401).send(errorMessage) |
299 | } | 284 | } |
300 | 285 | ||
301 | token = content.token; | 286 | token = content.token |
302 | } catch (error) { | 287 | } catch (error) { |
303 | return response.status(401).send({ | 288 | return response.status(401).send({ |
304 | message: 'Cannot login to Franz', | 289 | message: 'Cannot login to Franz', |
305 | error: error, | 290 | error: error, |
306 | }); | 291 | }) |
307 | } | 292 | } |
308 | 293 | ||
309 | // Get user information | 294 | // Get user information |
310 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | 295 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
311 | let userInf: any = false; | 296 | let userInf: any = false |
312 | try { | 297 | try { |
313 | userInf = await franzRequest('me', 'GET', token); | 298 | userInf = await franzRequest('me', 'GET', token) |
314 | } catch (error) { | 299 | } catch (error) { |
315 | const errorMessage = `Could not get your user info from Franz. Please check your credentials or try again later.\nError: ${error}`; | 300 | const errorMessage = `Could not get your user info from Franz. Please check your credentials or try again later.\nError: ${error}` |
316 | return response.status(401).send(errorMessage); | 301 | return response.status(401).send(errorMessage) |
317 | } | 302 | } |
318 | if (!userInf) { | 303 | if (!userInf) { |
319 | const errorMessage = | 304 | const errorMessage = |
320 | 'Could not get your user info from Franz. Please check your credentials or try again later'; | 305 | 'Could not get your user info from Franz. Please check your credentials or try again later' |
321 | return response.status(401).send(errorMessage); | 306 | return response.status(401).send(errorMessage) |
322 | } | 307 | } |
323 | 308 | ||
324 | // Create user in DB | 309 | // Create user in DB |
325 | let user; | 310 | let user |
326 | try { | 311 | try { |
327 | user = await User.create({ | 312 | user = await User.create({ |
328 | email: userInf.email, | 313 | email: userInf.email, |
329 | password: hashedPassword, | 314 | password: hashedPassword, |
330 | username: userInf.firstname, | 315 | username: userInf.firstname, |
331 | lastname: userInf.lastname, | 316 | lastname: userInf.lastname, |
332 | }); | 317 | }) |
333 | } catch (error) { | 318 | } catch (error) { |
334 | const errorMessage = `Could not create your user in our system.\nError: ${error}`; | 319 | const errorMessage = `Could not create your user in our system.\nError: ${error}` |
335 | return response.status(401).send(errorMessage); | 320 | return response.status(401).send(errorMessage) |
336 | } | 321 | } |
337 | 322 | ||
338 | const serviceIdTranslation = {}; | 323 | const serviceIdTranslation = {} |
339 | 324 | ||
340 | // Import services | 325 | // Import services |
341 | try { | 326 | try { |
342 | const services = await franzRequest('me/services', 'GET', token); | 327 | const services = await franzRequest('me/services', 'GET', token) |
343 | 328 | ||
344 | // @ts-expect-error | 329 | // @ts-expect-error |
345 | for (const service of services) { | 330 | for (const service of services) { |
346 | // Get new, unused uuid | 331 | // Get new, unused uuid |
347 | let serviceId; | 332 | let serviceId |
348 | do { | 333 | do { |
349 | serviceId = uuid(); | 334 | serviceId = uuid() |
350 | } while ( | 335 | } while ( |
351 | // eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member | 336 | // eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member |
352 | (await Service.query().where('serviceId', serviceId)).length > 0 | 337 | (await Service.query().where('serviceId', serviceId)).length > 0 |
353 | ); | 338 | ) |
354 | 339 | ||
355 | // eslint-disable-next-line no-await-in-loop | 340 | // eslint-disable-next-line no-await-in-loop |
356 | await Service.create({ | 341 | await Service.create({ |
@@ -359,34 +344,34 @@ export default class UsersController { | |||
359 | name: service.name, | 344 | name: service.name, |
360 | recipeId: service.recipeId, | 345 | recipeId: service.recipeId, |
361 | settings: JSON.stringify(service), | 346 | settings: JSON.stringify(service), |
362 | }); | 347 | }) |
363 | 348 | ||
364 | // @ts-expect-error | 349 | // @ts-expect-error |
365 | serviceIdTranslation[service.id] = serviceId; | 350 | serviceIdTranslation[service.id] = serviceId |
366 | } | 351 | } |
367 | } catch (error) { | 352 | } catch (error) { |
368 | const errorMessage = `Could not import your services into our system.\nError: ${error}`; | 353 | const errorMessage = `Could not import your services into our system.\nError: ${error}` |
369 | return response.status(401).send(errorMessage); | 354 | return response.status(401).send(errorMessage) |
370 | } | 355 | } |
371 | 356 | ||
372 | // Import workspaces | 357 | // Import workspaces |
373 | try { | 358 | try { |
374 | const workspaces = await franzRequest('workspace', 'GET', token); | 359 | const workspaces = await franzRequest('workspace', 'GET', token) |
375 | 360 | ||
376 | // @ts-expect-error | 361 | // @ts-expect-error |
377 | for (const workspace of workspaces) { | 362 | for (const workspace of workspaces) { |
378 | let workspaceId; | 363 | let workspaceId |
379 | do { | 364 | do { |
380 | workspaceId = uuid(); | 365 | workspaceId = uuid() |
381 | } while ( | 366 | } while ( |
382 | // eslint-disable-next-line unicorn/no-await-expression-member, no-await-in-loop | 367 | // eslint-disable-next-line unicorn/no-await-expression-member, no-await-in-loop |
383 | (await Workspace.query().where('workspaceId', workspaceId)).length > 0 | 368 | (await Workspace.query().where('workspaceId', workspaceId)).length > 0 |
384 | ); | 369 | ) |
385 | 370 | ||
386 | const services = workspace.services.map( | 371 | const services = workspace.services.map( |
387 | // @ts-expect-error | 372 | // @ts-expect-error |
388 | service => serviceIdTranslation[service], | 373 | (service) => serviceIdTranslation[service] |
389 | ); | 374 | ) |
390 | 375 | ||
391 | // eslint-disable-next-line no-await-in-loop | 376 | // eslint-disable-next-line no-await-in-loop |
392 | await Workspace.create({ | 377 | await Workspace.create({ |
@@ -396,15 +381,15 @@ export default class UsersController { | |||
396 | order: workspace.order, | 381 | order: workspace.order, |
397 | services: JSON.stringify(services), | 382 | services: JSON.stringify(services), |
398 | data: JSON.stringify({}), | 383 | data: JSON.stringify({}), |
399 | }); | 384 | }) |
400 | } | 385 | } |
401 | } catch (error) { | 386 | } catch (error) { |
402 | const errorMessage = `Could not import your workspaces into our system.\nError: ${error}`; | 387 | const errorMessage = `Could not import your workspaces into our system.\nError: ${error}` |
403 | return response.status(401).send(errorMessage); | 388 | return response.status(401).send(errorMessage) |
404 | } | 389 | } |
405 | 390 | ||
406 | return response.send( | 391 | return response.send( |
407 | 'Your account has been imported. You can now use your Franz/Ferdi account in Ferdium.', | 392 | 'Your account has been imported. You can now use your Franz/Ferdi account in Ferdium.' |
408 | ); | 393 | ) |
409 | } | 394 | } |
410 | } | 395 | } |
diff --git a/app/Controllers/Http/WorkspaceController.ts b/app/Controllers/Http/WorkspaceController.ts index 70af343..a2bc54e 100644 --- a/app/Controllers/Http/WorkspaceController.ts +++ b/app/Controllers/Http/WorkspaceController.ts | |||
@@ -1,53 +1,53 @@ | |||
1 | import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | 1 | import type { HttpContext } from '@adonisjs/core/http' |
2 | import { validator, schema } from '@ioc:Adonis/Core/Validator'; | 2 | import { validator, schema } from '@adonisjs/validator' |
3 | import Workspace from 'App/Models/Workspace'; | 3 | import Workspace from '#app/Models/Workspace' |
4 | import { v4 as uuid } from 'uuid'; | 4 | import { v4 as uuid } from 'uuid' |
5 | 5 | ||
6 | const createSchema = schema.create({ | 6 | const createSchema = schema.create({ |
7 | name: schema.string(), | 7 | name: schema.string(), |
8 | }); | 8 | }) |
9 | 9 | ||
10 | const editSchema = schema.create({ | 10 | const editSchema = schema.create({ |
11 | name: schema.string(), | 11 | name: schema.string(), |
12 | }); | 12 | }) |
13 | 13 | ||
14 | const deleteSchema = schema.create({ | 14 | const deleteSchema = schema.create({ |
15 | id: schema.string(), | 15 | id: schema.string(), |
16 | }); | 16 | }) |
17 | 17 | ||
18 | export default class WorkspaceController { | 18 | export default class WorkspaceController { |
19 | // Create a new workspace for user | 19 | // Create a new workspace for user |
20 | public async create({ request, response, auth }: HttpContextContract) { | 20 | public async create({ request, response, auth }: HttpContext) { |
21 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. | 21 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. |
22 | const user = auth.user ?? request.user; | 22 | const user = auth.user ?? request.user |
23 | 23 | ||
24 | if (!user) { | 24 | if (!user) { |
25 | return response.unauthorized('Missing or invalid api token'); | 25 | return response.unauthorized('Missing or invalid api token') |
26 | } | 26 | } |
27 | 27 | ||
28 | // Validate user input | 28 | // Validate user input |
29 | let data; | 29 | let data |
30 | try { | 30 | try { |
31 | data = await request.validate({ schema: createSchema }); | 31 | data = await request.validate({ schema: createSchema }) |
32 | } catch (error) { | 32 | } catch (error) { |
33 | return response.status(401).send({ | 33 | return response.status(401).send({ |
34 | message: 'Invalid POST arguments', | 34 | message: 'Invalid POST arguments', |
35 | messages: error.messages, | 35 | messages: error.messages, |
36 | status: 401, | 36 | status: 401, |
37 | }); | 37 | }) |
38 | } | 38 | } |
39 | 39 | ||
40 | // Get new, unused uuid | 40 | // Get new, unused uuid |
41 | let workspaceId; | 41 | let workspaceId |
42 | do { | 42 | do { |
43 | workspaceId = uuid(); | 43 | workspaceId = uuid() |
44 | } while ( | 44 | } while ( |
45 | // eslint-disable-next-line unicorn/no-await-expression-member, no-await-in-loop | 45 | // eslint-disable-next-line unicorn/no-await-expression-member, no-await-in-loop |
46 | (await Workspace.query().where('workspaceId', workspaceId)).length > 0 | 46 | (await Workspace.query().where('workspaceId', workspaceId)).length > 0 |
47 | ); | 47 | ) |
48 | 48 | ||
49 | // eslint-disable-next-line unicorn/no-await-expression-member | 49 | // eslint-disable-next-line unicorn/no-await-expression-member |
50 | const order = (await user.related('workspaces').query()).length; | 50 | const order = (await user.related('workspaces').query()).length |
51 | 51 | ||
52 | await Workspace.create({ | 52 | await Workspace.create({ |
53 | userId: user.id, | 53 | userId: user.id, |
@@ -56,7 +56,7 @@ export default class WorkspaceController { | |||
56 | order, | 56 | order, |
57 | services: JSON.stringify([]), | 57 | services: JSON.stringify([]), |
58 | data: JSON.stringify(data), | 58 | data: JSON.stringify(data), |
59 | }); | 59 | }) |
60 | 60 | ||
61 | return response.send({ | 61 | return response.send({ |
62 | userId: user.id, | 62 | userId: user.id, |
@@ -64,30 +64,30 @@ export default class WorkspaceController { | |||
64 | id: workspaceId, | 64 | id: workspaceId, |
65 | order, | 65 | order, |
66 | workspaces: [], | 66 | workspaces: [], |
67 | }); | 67 | }) |
68 | } | 68 | } |
69 | 69 | ||
70 | public async edit({ request, response, auth, params }: HttpContextContract) { | 70 | public async edit({ request, response, auth, params }: HttpContext) { |
71 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. | 71 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. |
72 | const user = auth.user ?? request.user; | 72 | const user = auth.user ?? request.user |
73 | 73 | ||
74 | if (!user) { | 74 | if (!user) { |
75 | return response.unauthorized('Missing or invalid api token'); | 75 | return response.unauthorized('Missing or invalid api token') |
76 | } | 76 | } |
77 | 77 | ||
78 | // Validate user input | 78 | // Validate user input |
79 | try { | 79 | try { |
80 | await request.validate({ schema: editSchema }); | 80 | await request.validate({ schema: editSchema }) |
81 | } catch (error) { | 81 | } catch (error) { |
82 | return response.status(401).send({ | 82 | return response.status(401).send({ |
83 | message: 'Invalid POST arguments', | 83 | message: 'Invalid POST arguments', |
84 | messages: error.messages, | 84 | messages: error.messages, |
85 | status: 401, | 85 | status: 401, |
86 | }); | 86 | }) |
87 | } | 87 | } |
88 | 88 | ||
89 | const data = request.all(); | 89 | const data = request.all() |
90 | const { id } = params; | 90 | const { id } = params |
91 | 91 | ||
92 | // Update data in database | 92 | // Update data in database |
93 | await Workspace.query() | 93 | await Workspace.query() |
@@ -96,13 +96,13 @@ export default class WorkspaceController { | |||
96 | .update({ | 96 | .update({ |
97 | name: data.name, | 97 | name: data.name, |
98 | services: JSON.stringify(data.services), | 98 | services: JSON.stringify(data.services), |
99 | }); | 99 | }) |
100 | 100 | ||
101 | // Get updated row | 101 | // Get updated row |
102 | const workspace = await Workspace.query() | 102 | const workspace = await Workspace.query() |
103 | .where('workspaceId', id) | 103 | .where('workspaceId', id) |
104 | .where('userId', user.id) | 104 | .where('userId', user.id) |
105 | .firstOrFail(); | 105 | .firstOrFail() |
106 | 106 | ||
107 | return response.send({ | 107 | return response.send({ |
108 | id: workspace.workspaceId, | 108 | id: workspace.workspaceId, |
@@ -110,62 +110,54 @@ export default class WorkspaceController { | |||
110 | order: workspace.order, | 110 | order: workspace.order, |
111 | services: data.services, | 111 | services: data.services, |
112 | userId: user.id, | 112 | userId: user.id, |
113 | }); | 113 | }) |
114 | } | 114 | } |
115 | 115 | ||
116 | public async delete({ | 116 | public async delete({ request, response, auth, params }: HttpContext) { |
117 | request, | ||
118 | response, | ||
119 | auth, | ||
120 | params, | ||
121 | }: HttpContextContract) { | ||
122 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. | 117 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. |
123 | const user = auth.user ?? request.user; | 118 | const user = auth.user ?? request.user |
124 | 119 | ||
125 | if (!user) { | 120 | if (!user) { |
126 | return response.unauthorized('Missing or invalid api token'); | 121 | return response.unauthorized('Missing or invalid api token') |
127 | } | 122 | } |
128 | 123 | ||
129 | // Validate user input | 124 | // Validate user input |
130 | let data; | 125 | let data |
131 | try { | 126 | try { |
132 | data = await validator.validate({ | 127 | data = await validator.validate({ |
133 | data: params, | 128 | data: params, |
134 | schema: deleteSchema, | 129 | schema: deleteSchema, |
135 | }); | 130 | }) |
136 | } catch (error) { | 131 | } catch (error) { |
137 | return response.status(401).send({ | 132 | return response.status(401).send({ |
138 | message: 'Invalid arguments', | 133 | message: 'Invalid arguments', |
139 | messages: error.messages, | 134 | messages: error.messages, |
140 | status: 401, | 135 | status: 401, |
141 | }); | 136 | }) |
142 | } | 137 | } |
143 | 138 | ||
144 | const { id } = data; | 139 | const { id } = data |
145 | 140 | ||
146 | // Update data in database | 141 | // Update data in database |
147 | await Workspace.query() | 142 | await Workspace.query().where('workspaceId', id).where('userId', user.id).delete() |
148 | .where('workspaceId', id) | ||
149 | .where('userId', user.id) | ||
150 | .delete(); | ||
151 | 143 | ||
152 | return response.send({ | 144 | return response.send({ |
153 | message: 'Successfully deleted workspace', | 145 | message: 'Successfully deleted workspace', |
154 | }); | 146 | }) |
155 | } | 147 | } |
156 | 148 | ||
157 | // List all workspaces a user has created | 149 | // List all workspaces a user has created |
158 | public async list({ request, response, auth }: HttpContextContract) { | 150 | public async list({ request, response, auth }: HttpContext) { |
159 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. | 151 | // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. |
160 | const user = auth.user ?? request.user; | 152 | const user = auth.user ?? request.user |
161 | 153 | ||
162 | if (!user) { | 154 | if (!user) { |
163 | return response.unauthorized('Missing or invalid api token'); | 155 | return response.unauthorized('Missing or invalid api token') |
164 | } | 156 | } |
165 | 157 | ||
166 | const workspaces = await user.related('workspaces').query(); | 158 | const workspaces = await user.related('workspaces').query() |
167 | // Convert to array with all data Franz wants | 159 | // Convert to array with all data Franz wants |
168 | let workspacesArray: object[] = []; | 160 | let workspacesArray: object[] = [] |
169 | if (workspaces) { | 161 | if (workspaces) { |
170 | // eslint-disable-next-line @typescript-eslint/no-explicit-any | 162 | // eslint-disable-next-line @typescript-eslint/no-explicit-any |
171 | workspacesArray = workspaces.map((workspace: any) => ({ | 163 | workspacesArray = workspaces.map((workspace: any) => ({ |
@@ -177,9 +169,9 @@ export default class WorkspaceController { | |||
177 | ? JSON.parse(workspace.services) | 169 | ? JSON.parse(workspace.services) |
178 | : workspace.services, | 170 | : workspace.services, |
179 | userId: user.id, | 171 | userId: user.id, |
180 | })); | 172 | })) |
181 | } | 173 | } |
182 | 174 | ||
183 | return response.send(workspacesArray); | 175 | return response.send(workspacesArray) |
184 | } | 176 | } |
185 | } | 177 | } |