aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorLibravatar MCMXC <16797721+mcmxcdev@users.noreply.github.com>2024-02-10 18:19:14 -0700
committerLibravatar Vijay A <vraravam@users.noreply.github.com>2024-02-13 06:59:44 +0530
commit7584d2d7a7110aef0331ebfa178b2295842c59fa (patch)
tree900cd71237e6231b57936fcce77ff229cd459041 /app
parentupgrade recipes submodule (diff)
downloadferdium-server-7584d2d7a7110aef0331ebfa178b2295842c59fa.tar.gz
ferdium-server-7584d2d7a7110aef0331ebfa178b2295842c59fa.tar.zst
ferdium-server-7584d2d7a7110aef0331ebfa178b2295842c59fa.zip
refactor: project maintenance
- work in progress
Diffstat (limited to 'app')
-rw-r--r--app/Controllers/Http/Api/Static/AnnouncementsController.ts20
-rw-r--r--app/Controllers/Http/Api/Static/EmptyController.ts6
-rw-r--r--app/Controllers/Http/Api/Static/FeaturesController.ts6
-rw-r--r--app/Controllers/Http/Dashboard/AccountController.ts40
-rw-r--r--app/Controllers/Http/Dashboard/DataController.ts12
-rw-r--r--app/Controllers/Http/Dashboard/DeleteController.ts14
-rw-r--r--app/Controllers/Http/Dashboard/ExportController.ts29
-rw-r--r--app/Controllers/Http/Dashboard/ForgotPasswordController.ts22
-rw-r--r--app/Controllers/Http/Dashboard/LogOutController.ts8
-rw-r--r--app/Controllers/Http/Dashboard/LoginController.ts58
-rw-r--r--app/Controllers/Http/Dashboard/ResetPasswordController.ts51
-rw-r--r--app/Controllers/Http/Dashboard/TransferController.ts77
-rw-r--r--app/Controllers/Http/DashboardController.ts2
-rw-r--r--app/Controllers/Http/HealthController.ts2
-rw-r--r--app/Controllers/Http/HomeController.ts4
-rw-r--r--app/Controllers/Http/RecipeController.ts179
-rw-r--r--app/Controllers/Http/ServiceController.ts204
-rw-r--r--app/Controllers/Http/StaticsController.ts2
-rw-r--r--app/Controllers/Http/UserController.ts239
-rw-r--r--app/Controllers/Http/WorkspaceController.ts98
-rw-r--r--app/Exceptions/Handler.ts8
-rw-r--r--app/Middleware/AllowGuestOnly.ts33
-rw-r--r--app/Middleware/Auth.ts61
-rw-r--r--app/Middleware/Dashboard.ts15
-rw-r--r--app/Middleware/SilentAuth.ts11
-rw-r--r--app/Models/Recipe.ts16
-rw-r--r--app/Models/Service.ts25
-rw-r--r--app/Models/Token.ts27
-rw-r--r--app/Models/User.ts85
-rw-r--r--app/Models/Workspace.ts27
30 files changed, 629 insertions, 752 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2import Application from '@ioc:Adonis/Core/Application'; 2import { app } from '@adonisjs/core/services/app'
3import path from 'node:path'; 3import path from 'node:path'
4import fs from 'fs-extra'; 4import fs from 'fs-extra'
5 5
6export default class AnnouncementsController { 6export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2 2
3export default class EmptyController { 3export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2 2
3export default class FeaturesController { 3export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2import { schema, rules, validator } from '@ioc:Adonis/Core/Validator'; 2import { schema, rules, validator } from '@adonisjs/validator'
3import crypto from 'node:crypto'; 3import crypto from 'node:crypto'
4 4
5export default class AccountController { 5export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2 2
3export default class DataController { 3export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2 2
3export default class DeleteController { 3export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import 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
4function deepParseToJSON(obj: any): Record<string, unknown> { 4function 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2import { schema, rules, validator } from '@ioc:Adonis/Core/Validator'; 2import { schema, rules, validator } from '@adonisjs/validator'
3import User from 'App/Models/User'; 3import User from '#app/Models/User'
4 4
5export default class ForgotPasswordController { 5export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2 2
3export default class LogOutController { 3export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2import { schema, rules, validator } from '@ioc:Adonis/Core/Validator'; 2import { schema, rules, validator } from '@adonisjs/validator'
3import User from 'App/Models/User'; 3import User from '#app/Models/User'
4import crypto from 'node:crypto'; 4import crypto from 'node:crypto'
5import { handleVerifyAndReHash } from '../../../../helpers/PasswordHash'; 5import { handleVerifyAndReHash } from '../../../../helpers/PasswordHash.js'
6 6
7export default class LoginController { 7export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2import { schema, rules, validator } from '@ioc:Adonis/Core/Validator'; 2import { schema, rules, validator } from '@adonisjs/validator'
3import Token from 'App/Models/Token'; 3import Token from '#app/Models/Token'
4import moment from 'moment'; 4import moment from 'moment'
5import crypto from 'node:crypto'; 5import crypto from 'node:crypto'
6 6
7export default class ResetPasswordController { 7export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2import { schema, validator } from '@ioc:Adonis/Core/Validator'; 2import { schema, validator } from '@adonisjs/validator'
3import Service from 'App/Models/Service'; 3import Service from '#app/Models/Service'
4import Workspace from 'App/Models/Workspace'; 4import Workspace from '#app/Models/Workspace'
5import { v4 as uuidv4 } from 'uuid'; 5import { v4 as uuidv4 } from 'uuid'
6 6
7const importSchema = schema.create({ 7const 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
15export default class TransferController { 15export 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
3export default class DashboardController {} 3export 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
3export default class HomeController { 3export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2import fs from 'fs-extra'; 2import fs from 'fs-extra'
3import Application from '@ioc:Adonis/Core/Application'; 3import { app } from '@adonisjs/core/services/app'
4import path from 'node:path'; 4import path from 'node:path'
5import Recipe from 'App/Models/Recipe'; 5import Recipe from '#app/Models/Recipe'
6import { isCreationEnabled } from 'Config/app'; 6import { isCreationEnabled } from '#config/app'
7import { validator, schema, rules } from '@ioc:Adonis/Core/Validator'; 7import { validator, schema, rules } from '@adonisjs/validator'
8import targz from 'targz'; 8import targz from 'targz'
9import semver from 'semver'; 9import semver from 'semver'
10import Drive from '@ioc:Adonis/Core/Drive'; 10import 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
23const searchSchema = schema.create({ 23const searchSchema = schema.create({
24 needle: schema.string(), 24 needle: schema.string(),
25}); 25})
26 26
27const downloadSchema = schema.create({ 27const 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
33const compress = (src: string, dest: string) => 33const 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
50export default class RecipesController { 50export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2import { schema } from '@ioc:Adonis/Core/Validator'; 2import { schema } from '@adonisjs/validator'
3import Service from 'App/Models/Service'; 3import Service from '#app/Models/Service'
4import { url } from 'Config/app'; 4import { url } from '#config/app'
5import { v4 as uuid } from 'uuid'; 5import { v4 as uuid } from 'uuid'
6import * as fs from 'fs-extra'; 6import * as fs from 'fs-extra'
7import path from 'node:path'; 7import path from 'node:path'
8import Application from '@ioc:Adonis/Core/Application'; 8import { app } from '@adonisjs/core/services/app'
9import sanitize from 'sanitize-filename'; 9import sanitize from 'sanitize-filename'
10 10
11const createSchema = schema.create({ 11const createSchema = schema.create({
12 name: schema.string(), 12 name: schema.string(),
13 recipeId: schema.string(), 13 recipeId: schema.string(),
14}); 14})
15 15
16export default class ServiceController { 16export 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
3export default class StaticsController {} 3export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2import { schema, rules } from '@ioc:Adonis/Core/Validator'; 2import { schema, rules } from '@adonisjs/validator'
3import User from 'App/Models/User'; 3import User from '#app/Models/User'
4import { connectWithFranz, isRegistrationEnabled } from '../../../config/app'; 4import { connectWithFranz, isRegistrationEnabled } from '../../../config/app.js'
5import crypto from 'node:crypto'; 5import crypto from 'node:crypto'
6import { v4 as uuid } from 'uuid'; 6import { v4 as uuid } from 'uuid'
7import Workspace from 'App/Models/Workspace'; 7import Workspace from '#app/Models/Workspace'
8import Service from 'App/Models/Service'; 8import Service from '#app/Models/Service'
9import 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
12import { handleVerifyAndReHash } from '../../../helpers/PasswordHash'; 11import { handleVerifyAndReHash } from '../../../helpers/PasswordHash.js'
13 12
14const newPostSchema = schema.create({ 13const 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
24const franzImportSchema = schema.create({ 20const 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
34const franzRequest = (route: any, method: any, auth: any) => 27const 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
55export default class UsersController { 48export 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 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2import { validator, schema } from '@ioc:Adonis/Core/Validator'; 2import { validator, schema } from '@adonisjs/validator'
3import Workspace from 'App/Models/Workspace'; 3import Workspace from '#app/Models/Workspace'
4import { v4 as uuid } from 'uuid'; 4import { v4 as uuid } from 'uuid'
5 5
6const createSchema = schema.create({ 6const createSchema = schema.create({
7 name: schema.string(), 7 name: schema.string(),
8}); 8})
9 9
10const editSchema = schema.create({ 10const editSchema = schema.create({
11 name: schema.string(), 11 name: schema.string(),
12}); 12})
13 13
14const deleteSchema = schema.create({ 14const deleteSchema = schema.create({
15 id: schema.string(), 15 id: schema.string(),
16}); 16})
17 17
18export default class WorkspaceController { 18export 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}
diff --git a/app/Exceptions/Handler.ts b/app/Exceptions/Handler.ts
index 35c77d0..b13126d 100644
--- a/app/Exceptions/Handler.ts
+++ b/app/Exceptions/Handler.ts
@@ -13,11 +13,11 @@
13| 13|
14*/ 14*/
15 15
16import Logger from '@ioc:Adonis/Core/Logger'; 16import logger from '@adonisjs/core/services/logger'
17import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'; 17import { ExceptionHandler as AdonisExceptionHandler } from '@adonisjs/core/http'
18 18
19export default class ExceptionHandler extends HttpExceptionHandler { 19export default class ExceptionHandler extends AdonisExceptionHandler {
20 constructor() { 20 constructor() {
21 super(Logger); 21 super(logger)
22 } 22 }
23} 23}
diff --git a/app/Middleware/AllowGuestOnly.ts b/app/Middleware/AllowGuestOnly.ts
index ee43571..5ef5c34 100644
--- a/app/Middleware/AllowGuestOnly.ts
+++ b/app/Middleware/AllowGuestOnly.ts
@@ -1,6 +1,6 @@
1import { GuardsList } from '@ioc:Adonis/Addons/Auth'; 1import { GuardsList } from '@ioc:Adonis/Addons/Auth'
2import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 2import { HttpContext } from '@adonisjs/core/http'
3import { AuthenticationException } from '@adonisjs/auth/build/standalone'; 3import { AuthenticationException } from '@adonisjs/auth/build/standalone'
4 4
5/** 5/**
6 * This is actually a reverted a reverted auth middleware available in ./Auth.ts 6 * This is actually a reverted a reverted auth middleware available in ./Auth.ts
@@ -10,27 +10,24 @@ export default class GuestMiddleware {
10 /** 10 /**
11 * The URL to redirect to when request is authorized 11 * The URL to redirect to when request is authorized
12 */ 12 */
13 protected redirectTo = '/dashboard'; 13 protected redirectTo = '/dashboard'
14 14
15 protected async authenticate( 15 protected async authenticate(auth: HttpContext['auth'], guards: (keyof GuardsList)[]) {
16 auth: HttpContextContract['auth'], 16 let guardLastAttempted: string | undefined
17 guards: (keyof GuardsList)[],
18 ) {
19 let guardLastAttempted: string | undefined;
20 17
21 for (const guard of guards) { 18 for (const guard of guards) {
22 guardLastAttempted = guard; 19 guardLastAttempted = guard
23 20
24 // eslint-disable-next-line no-await-in-loop 21 // eslint-disable-next-line no-await-in-loop
25 if (await auth.use(guard).check()) { 22 if (await auth.use(guard).check()) {
26 auth.defaultGuard = guard; 23 auth.defaultGuard = guard
27 24
28 throw new AuthenticationException( 25 throw new AuthenticationException(
29 'Unauthorized access', 26 'Unauthorized access',
30 'E_UNAUTHORIZED_ACCESS', 27 'E_UNAUTHORIZED_ACCESS',
31 guardLastAttempted, 28 guardLastAttempted,
32 this.redirectTo, 29 this.redirectTo
33 ); 30 )
34 } 31 }
35 } 32 }
36 } 33 }
@@ -39,18 +36,18 @@ export default class GuestMiddleware {
39 * Handle request 36 * Handle request
40 */ 37 */
41 public async handle( 38 public async handle(
42 { auth }: HttpContextContract, 39 { auth }: HttpContext,
43 next: () => Promise<void>, 40 next: () => Promise<void>,
44 customGuards: (keyof GuardsList)[], 41 customGuards: (keyof GuardsList)[]
45 ) { 42 ) {
46 /** 43 /**
47 * Uses the user defined guards or the default guard mentioned in 44 * Uses the user defined guards or the default guard mentioned in
48 * the config file 45 * the config file
49 */ 46 */
50 const guards = customGuards.length > 0 ? customGuards : [auth.name]; 47 const guards = customGuards.length > 0 ? customGuards : [auth.name]
51 48
52 await this.authenticate(auth, guards); 49 await this.authenticate(auth, guards)
53 50
54 await next(); 51 await next()
55 } 52 }
56} 53}
diff --git a/app/Middleware/Auth.ts b/app/Middleware/Auth.ts
index d0b212c..29620bb 100644
--- a/app/Middleware/Auth.ts
+++ b/app/Middleware/Auth.ts
@@ -1,9 +1,9 @@
1import { GuardsList } from '@ioc:Adonis/Addons/Auth'; 1import { GuardsList } from '@ioc:Adonis/Addons/Auth'
2import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 2import { HttpContext } from '@adonisjs/core/http'
3import { AuthenticationException } from '@adonisjs/auth/build/standalone'; 3import { AuthenticationException } from '@adonisjs/auth/build/standalone'
4import * as jose from 'jose'; 4import * as jose from 'jose'
5import { appKey } from 'Config/app'; 5import { appKey } from '#config/app'
6import User from 'App/Models/User'; 6import User from '#app/Models/User'
7 7
8/** 8/**
9 * Auth middleware is meant to restrict un-authenticated access to a given route 9 * Auth middleware is meant to restrict un-authenticated access to a given route
@@ -16,7 +16,7 @@ export default class AuthMiddleware {
16 /** 16 /**
17 * The URL to redirect to when request is Unauthorized 17 * The URL to redirect to when request is Unauthorized
18 */ 18 */
19 protected redirectTo = '/user/login'; 19 protected redirectTo = '/user/login'
20 20
21 /** 21 /**
22 * Authenticates the current HTTP request against a custom set of defined 22 * Authenticates the current HTTP request against a custom set of defined
@@ -27,9 +27,9 @@ export default class AuthMiddleware {
27 * during the current request. 27 * during the current request.
28 */ 28 */
29 protected async authenticate( 29 protected async authenticate(
30 auth: HttpContextContract['auth'], 30 auth: HttpContext['auth'],
31 guards: (keyof GuardsList)[], 31 guards: (keyof GuardsList)[],
32 request: HttpContextContract['request'], 32 request: HttpContext['request']
33 ) { 33 ) {
34 /** 34 /**
35 * Hold reference to the guard last attempted within the for loop. We pass 35 * Hold reference to the guard last attempted within the for loop. We pass
@@ -37,15 +37,15 @@ export default class AuthMiddleware {
37 * it can decide the correct response behavior based upon the guard 37 * it can decide the correct response behavior based upon the guard
38 * driver 38 * driver
39 */ 39 */
40 let guardLastAttempted: string | undefined; 40 let guardLastAttempted: string | undefined
41 41
42 for (const guard of guards) { 42 for (const guard of guards) {
43 guardLastAttempted = guard; 43 guardLastAttempted = guard
44 44
45 let isLoggedIn = false; 45 let isLoggedIn = false
46 try { 46 try {
47 // eslint-disable-next-line no-await-in-loop 47 // eslint-disable-next-line no-await-in-loop
48 isLoggedIn = await auth.use(guard).check(); 48 isLoggedIn = await auth.use(guard).check()
49 } catch { 49 } catch {
50 // Silent fail to allow the rest of the code to handle the error 50 // Silent fail to allow the rest of the code to handle the error
51 } 51 }
@@ -56,25 +56,22 @@ export default class AuthMiddleware {
56 * the rest of the request, since the user authenticated 56 * the rest of the request, since the user authenticated
57 * succeeded here 57 * succeeded here
58 */ 58 */
59 auth.defaultGuard = guard; 59 auth.defaultGuard = guard
60 return; 60 return
61 } 61 }
62 } 62 }
63 63
64 // Manually try authenticating using the JWT (verfiy signature required) 64 // Manually try authenticating using the JWT (verfiy signature required)
65 // Legacy support for JWTs so that the client still works (older than 2.0.0) 65 // Legacy support for JWTs so that the client still works (older than 2.0.0)
66 const authToken = request.headers().authorization?.split(' ')[1]; 66 const authToken = request.headers().authorization?.split(' ')[1]
67 if (authToken) { 67 if (authToken) {
68 try { 68 try {
69 const jwt = await jose.jwtVerify( 69 const jwt = await jose.jwtVerify(authToken, new TextEncoder().encode(appKey))
70 authToken, 70 const { uid } = jwt.payload
71 new TextEncoder().encode(appKey),
72 );
73 const { uid } = jwt.payload;
74 71
75 // @ts-expect-error 72 // @ts-expect-error
76 request.user = await User.findOrFail(uid); 73 request.user = await User.findOrFail(uid)
77 return; 74 return
78 } catch { 75 } catch {
79 // Silent fail to allow the rest of the code to handle the error 76 // Silent fail to allow the rest of the code to handle the error
80 } 77 }
@@ -87,32 +84,32 @@ export default class AuthMiddleware {
87 'Unauthorized access', 84 'Unauthorized access',
88 'E_UNAUTHORIZED_ACCESS', 85 'E_UNAUTHORIZED_ACCESS',
89 guardLastAttempted, 86 guardLastAttempted,
90 this.redirectTo, 87 this.redirectTo
91 ); 88 )
92 } 89 }
93 90
94 /** 91 /**
95 * Handle request 92 * Handle request
96 */ 93 */
97 public async handle( 94 public async handle(
98 { request, auth, response }: HttpContextContract, 95 { request, auth, response }: HttpContext,
99 next: () => Promise<void>, 96 next: () => Promise<void>,
100 customGuards: (keyof GuardsList)[], 97 customGuards: (keyof GuardsList)[]
101 ) { 98 ) {
102 /** 99 /**
103 * Uses the user defined guards or the default guard mentioned in 100 * Uses the user defined guards or the default guard mentioned in
104 * the config file 101 * the config file
105 */ 102 */
106 const guards = customGuards.length > 0 ? customGuards : [auth.name]; 103 const guards = customGuards.length > 0 ? customGuards : [auth.name]
107 try { 104 try {
108 await this.authenticate(auth, guards, request); 105 await this.authenticate(auth, guards, request)
109 } catch (error) { 106 } catch (error) {
110 // If the user is not authenticated and it is a web endpoint, redirect to the login page 107 // If the user is not authenticated and it is a web endpoint, redirect to the login page
111 if (guards.includes('web')) { 108 if (guards.includes('web')) {
112 return response.redirect(error.redirectTo); 109 return response.redirect(error.redirectTo)
113 } 110 }
114 throw error; 111 throw error
115 } 112 }
116 await next(); 113 await next()
117 } 114 }
118} 115}
diff --git a/app/Middleware/Dashboard.ts b/app/Middleware/Dashboard.ts
index 62deea0..f29794c 100644
--- a/app/Middleware/Dashboard.ts
+++ b/app/Middleware/Dashboard.ts
@@ -1,17 +1,14 @@
1import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import type { HttpContext } from '@adonisjs/core/http'
2import Config from '@ioc:Adonis/Core/Config'; 2import { Config } from '@adonisjs/core/config'
3 3
4export default class Dashboard { 4export default class Dashboard {
5 public async handle( 5 public async handle({ response }: HttpContext, next: () => Promise<void>) {
6 { response }: HttpContextContract,
7 next: () => Promise<void>,
8 ) {
9 if (Config.get('dashboard.enabled') === false) { 6 if (Config.get('dashboard.enabled') === false) {
10 response.send( 7 response.send(
11 'The user dashboard is disabled on this server\n\nIf you are the server owner, please set IS_DASHBOARD_ENABLED to true to enable the dashboard.', 8 'The user dashboard is disabled on this server\n\nIf you are the server owner, please set IS_DASHBOARD_ENABLED to true to enable the dashboard.'
12 ); 9 )
13 } else { 10 } else {
14 await next(); 11 await next()
15 } 12 }
16 } 13 }
17} 14}
diff --git a/app/Middleware/SilentAuth.ts b/app/Middleware/SilentAuth.ts
index ee73ec4..a7271d5 100644
--- a/app/Middleware/SilentAuth.ts
+++ b/app/Middleware/SilentAuth.ts
@@ -1,4 +1,4 @@
1import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; 1import { HttpContext } from '@adonisjs/core/http'
2 2
3/** 3/**
4 * Silent auth middleware can be used as a global middleware to silent check 4 * Silent auth middleware can be used as a global middleware to silent check
@@ -10,15 +10,12 @@ export default class SilentAuthMiddleware {
10 /** 10 /**
11 * Handle request 11 * Handle request
12 */ 12 */
13 public async handle( 13 public async handle({ auth }: HttpContext, next: () => Promise<void>) {
14 { auth }: HttpContextContract,
15 next: () => Promise<void>,
16 ) {
17 /** 14 /**
18 * Check if user is logged-in or not. If yes, then `ctx.auth.user` will be 15 * Check if user is logged-in or not. If yes, then `ctx.auth.user` will be
19 * set to the instance of the currently logged in user. 16 * set to the instance of the currently logged in user.
20 */ 17 */
21 await auth.check(); 18 await auth.check()
22 await next(); 19 await next()
23 } 20 }
24} 21}
diff --git a/app/Models/Recipe.ts b/app/Models/Recipe.ts
index fce5f3d..bca6e76 100644
--- a/app/Models/Recipe.ts
+++ b/app/Models/Recipe.ts
@@ -1,23 +1,23 @@
1import { DateTime } from 'luxon'; 1import { DateTime } from 'luxon'
2import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'; 2import { BaseModel, column } from '@adonisjs/lucid/orm'
3 3
4export default class Recipe extends BaseModel { 4export default class Recipe extends BaseModel {
5 @column({ isPrimary: true }) 5 @column({ isPrimary: true })
6 public id: number; 6 public id: number
7 7
8 @column() 8 @column()
9 public name: string; 9 public name: string
10 10
11 @column() 11 @column()
12 public recipeId: string; 12 public recipeId: string
13 13
14 // TODO: Type the data object. 14 // TODO: Type the data object.
15 @column() 15 @column()
16 public data: object; 16 public data: object
17 17
18 @column.dateTime({ autoCreate: true }) 18 @column.dateTime({ autoCreate: true })
19 public createdAt: DateTime; 19 public createdAt: DateTime
20 20
21 @column.dateTime({ autoCreate: true, autoUpdate: true }) 21 @column.dateTime({ autoCreate: true, autoUpdate: true })
22 public updatedAt: DateTime; 22 public updatedAt: DateTime
23} 23}
diff --git a/app/Models/Service.ts b/app/Models/Service.ts
index af1a8e1..0cd2afb 100644
--- a/app/Models/Service.ts
+++ b/app/Models/Service.ts
@@ -1,40 +1,41 @@
1import { DateTime } from 'luxon'; 1import { DateTime } from 'luxon'
2import { BaseModel, column, HasOne, hasOne } from '@ioc:Adonis/Lucid/Orm'; 2import { BaseModel, column, hasOne } from '@adonisjs/lucid/orm'
3import User from './User'; 3import User from './User.js'
4import type { HasOne } from '@adonisjs/lucid/types/relations'
4 5
5export default class Service extends BaseModel { 6export default class Service extends BaseModel {
6 @column({ isPrimary: true }) 7 @column({ isPrimary: true })
7 public id: number; 8 public id: number
8 9
9 @hasOne(() => User, { 10 @hasOne(() => User, {
10 foreignKey: 'userId', 11 foreignKey: 'userId',
11 }) 12 })
12 public user: HasOne<typeof User>; 13 public user: HasOne<typeof User>
13 14
14 @column({ 15 @column({
15 columnName: 'userId', 16 columnName: 'userId',
16 }) 17 })
17 public userId: number; 18 public userId: number
18 19
19 @column({ 20 @column({
20 columnName: 'serviceId', 21 columnName: 'serviceId',
21 }) 22 })
22 public serviceId: string; 23 public serviceId: string
23 24
24 @column() 25 @column()
25 public name: string; 26 public name: string
26 27
27 @column({ 28 @column({
28 columnName: 'recipeId', 29 columnName: 'recipeId',
29 }) 30 })
30 public recipeId: string; 31 public recipeId: string
31 32
32 @column() 33 @column()
33 public settings: string; 34 public settings: string
34 35
35 @column.dateTime({ autoCreate: true }) 36 @column.dateTime({ autoCreate: true })
36 public createdAt: DateTime; 37 public createdAt: DateTime
37 38
38 @column.dateTime({ autoCreate: true, autoUpdate: true }) 39 @column.dateTime({ autoCreate: true, autoUpdate: true })
39 public updatedAt: DateTime; 40 public updatedAt: DateTime
40} 41}
diff --git a/app/Models/Token.ts b/app/Models/Token.ts
index 4f85ebc..a8c29dd 100644
--- a/app/Models/Token.ts
+++ b/app/Models/Token.ts
@@ -1,38 +1,39 @@
1import { DateTime } from 'luxon'; 1import { DateTime } from 'luxon'
2import { BaseModel, column, HasOne, hasOne } from '@ioc:Adonis/Lucid/Orm'; 2import { BaseModel, column, hasOne } from '@adonisjs/lucid/orm'
3import User from './User'; 3import User from './User.js'
4import { HasOne } from '@adonisjs/lucid/types/relations'
4 5
5export default class Token extends BaseModel { 6export default class Token extends BaseModel {
6 @column({ isPrimary: true }) 7 @column({ isPrimary: true })
7 public id: number; 8 public id: number
8 9
9 @hasOne(() => User, { 10 @hasOne(() => User, {
10 localKey: 'user_id', 11 localKey: 'user_id',
11 foreignKey: 'id', 12 foreignKey: 'id',
12 }) 13 })
13 public user: HasOne<typeof User>; 14 public user: HasOne<typeof User>
14 15
15 @column() 16 @column()
16 public user_id: number; 17 public user_id: number
17 18
18 @column() 19 @column()
19 public token: string; 20 public token: string
20 21
21 @column() 22 @column()
22 public type: string; 23 public type: string
23 24
24 @column() 25 @column()
25 public is_revoked: boolean; 26 public is_revoked: boolean
26 27
27 @column() 28 @column()
28 public name: string; 29 public name: string
29 30
30 @column.dateTime() 31 @column.dateTime()
31 public expires_at: DateTime; 32 public expires_at: DateTime
32 33
33 @column.dateTime({ autoCreate: true }) 34 @column.dateTime({ autoCreate: true })
34 public created_at: DateTime; 35 public created_at: DateTime
35 36
36 @column.dateTime({ autoCreate: true, autoUpdate: true }) 37 @column.dateTime({ autoCreate: true, autoUpdate: true })
37 public updated_at: DateTime; 38 public updated_at: DateTime
38} 39}
diff --git a/app/Models/User.ts b/app/Models/User.ts
index 0b8e688..cc2c553 100644
--- a/app/Models/User.ts
+++ b/app/Models/User.ts
@@ -1,75 +1,70 @@
1import { DateTime } from 'luxon'; 1import { DateTime } from 'luxon'
2import { 2import { BaseModel, beforeSave, column, hasMany } from '@adonisjs/lucid/orm'
3 BaseModel, 3import hash from '@adonisjs/core/services/hash'
4 beforeSave, 4import emitter from '@adonisjs/core/services/emitter'
5 column, 5import moment from 'moment'
6 HasMany, 6import Encryption from '@ioc:Adonis/Core/Encryption'
7 hasMany, 7import randtoken from 'rand-token'
8} from '@ioc:Adonis/Lucid/Orm'; 8import Token from './Token.js'
9import Hash from '@ioc:Adonis/Core/Hash'; 9import Workspace from './Workspace.js'
10import Event from '@ioc:Adonis/Core/Event'; 10import Service from './Service.js'
11import moment from 'moment'; 11import mail from '@adonisjs/mail/services/main'
12import Encryption from '@ioc:Adonis/Core/Encryption'; 12import { url } from '#config/app'
13import randtoken from 'rand-token'; 13import { mailFrom } from '#config/dashboard'
14import Token from './Token'; 14import { HasMany } from '@adonisjs/lucid/types/relations'
15import Workspace from './Workspace';
16import Service from './Service';
17import Mail from '@ioc:Adonis/Addons/Mail';
18import { url } from 'Config/app';
19import { mailFrom } from 'Config/dashboard';
20 15
21export default class User extends BaseModel { 16export default class User extends BaseModel {
22 @column({ isPrimary: true }) 17 @column({ isPrimary: true })
23 public id: number; 18 public id: number
24 19
25 @column() 20 @column()
26 public email: string; 21 public email: string
27 22
28 @column() 23 @column()
29 public username: string; 24 public username: string
30 25
31 @column() 26 @column()
32 public password: string; 27 public password: string
33 28
34 @column() 29 @column()
35 public lastname: string; 30 public lastname: string
36 31
37 // TODO: Type the settings object. 32 // TODO: Type the settings object.
38 @column() 33 @column()
39 public settings: object; 34 public settings: object
40 35
41 @column.dateTime({ autoCreate: true }) 36 @column.dateTime({ autoCreate: true })
42 public created_at: DateTime; 37 public created_at: DateTime
43 38
44 @column.dateTime({ autoCreate: true, autoUpdate: true }) 39 @column.dateTime({ autoCreate: true, autoUpdate: true })
45 public updated_at: DateTime; 40 public updated_at: DateTime
46 41
47 @beforeSave() 42 @beforeSave()
48 public static async hashPassword(user: User) { 43 public static async hashPassword(user: User) {
49 if (user.$dirty.password) { 44 if (user.$dirty.password) {
50 user.password = await Hash.make(user.password); 45 user.password = await hash.make(user.password)
51 } 46 }
52 } 47 }
53 48
54 @hasMany(() => Token, { 49 @hasMany(() => Token, {
55 foreignKey: 'user_id', 50 foreignKey: 'user_id',
56 }) 51 })
57 public tokens: HasMany<typeof Token>; 52 public tokens: HasMany<typeof Token>
58 53
59 @hasMany(() => Service, { 54 @hasMany(() => Service, {
60 foreignKey: 'userId', 55 foreignKey: 'userId',
61 }) 56 })
62 public services: HasMany<typeof Service>; 57 public services: HasMany<typeof Service>
63 58
64 @hasMany(() => Workspace, { 59 @hasMany(() => Workspace, {
65 foreignKey: 'userId', 60 foreignKey: 'userId',
66 }) 61 })
67 public workspaces: HasMany<typeof Workspace>; 62 public workspaces: HasMany<typeof Workspace>
68 63
69 public async forgotPassword(): Promise<void> { 64 public async forgotPassword(): Promise<void> {
70 const token = await this.generateToken(this, 'forgot_password'); 65 const token = await this.generateToken(this, 'forgot_password')
71 66
72 await Mail.send(message => { 67 await mail.send((message) => {
73 message 68 message
74 .from(mailFrom) 69 .from(mailFrom)
75 .to(this.email) 70 .to(this.email)
@@ -78,13 +73,13 @@ export default class User extends BaseModel {
78 username: this.username, 73 username: this.username,
79 appUrl: url, 74 appUrl: url,
80 token: token, 75 token: token,
81 }); 76 })
82 }); 77 })
83 78
84 await Event.emit('forgot:password', { 79 await emitter.emit('forgot:password', {
85 user: this, 80 user: this,
86 token, 81 token,
87 }); 82 })
88 } 83 }
89 84
90 private async generateToken(user: User, type: string): Promise<string> { 85 private async generateToken(user: User, type: string): Promise<string> {
@@ -93,21 +88,17 @@ export default class User extends BaseModel {
93 .query() 88 .query()
94 .where('type', type) 89 .where('type', type)
95 .where('is_revoked', false) 90 .where('is_revoked', false)
96 .where( 91 .where('updated_at', '>=', moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'))
97 'updated_at',
98 '>=',
99 moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'),
100 );
101 92
102 const row = await query.first(); 93 const row = await query.first()
103 if (row) { 94 if (row) {
104 return row.token; 95 return row.token
105 } 96 }
106 97
107 const token = Encryption.encrypt(randtoken.generate(16)); 98 const token = Encryption.encrypt(randtoken.generate(16))
108 99
109 await user.related('tokens').create({ type, token }); 100 await user.related('tokens').create({ type, token })
110 101
111 return token; 102 return token
112 } 103 }
113} 104}
diff --git a/app/Models/Workspace.ts b/app/Models/Workspace.ts
index 8648e02..c960ae4 100644
--- a/app/Models/Workspace.ts
+++ b/app/Models/Workspace.ts
@@ -1,41 +1,42 @@
1import { DateTime } from 'luxon'; 1import { DateTime } from 'luxon'
2import { BaseModel, column, HasOne, hasOne } from '@ioc:Adonis/Lucid/Orm'; 2import { BaseModel, column, hasOne } from '@adonisjs/lucid/orm'
3import User from './User'; 3import User from './User.js'
4import { HasOne } from '@adonisjs/lucid/types/relations'
4 5
5export default class Workspace extends BaseModel { 6export default class Workspace extends BaseModel {
6 @column({ isPrimary: true }) 7 @column({ isPrimary: true })
7 public id: number; 8 public id: number
8 9
9 @column({ 10 @column({
10 columnName: 'workspaceId', 11 columnName: 'workspaceId',
11 }) 12 })
12 public workspaceId: string; 13 public workspaceId: string
13 14
14 @hasOne(() => User, { 15 @hasOne(() => User, {
15 foreignKey: 'userId', 16 foreignKey: 'userId',
16 }) 17 })
17 public user: HasOne<typeof User>; 18 public user: HasOne<typeof User>
18 19
19 @column({ 20 @column({
20 columnName: 'userId', 21 columnName: 'userId',
21 }) 22 })
22 public userId: number; 23 public userId: number
23 24
24 @column() 25 @column()
25 public name: string; 26 public name: string
26 27
27 @column() 28 @column()
28 public order: number; 29 public order: number
29 30
30 @column() 31 @column()
31 public services: string; 32 public services: string
32 33
33 @column() 34 @column()
34 public data: string; 35 public data: string
35 36
36 @column.dateTime({ autoCreate: true }) 37 @column.dateTime({ autoCreate: true })
37 public createdAt: DateTime; 38 public createdAt: DateTime
38 39
39 @column.dateTime({ autoCreate: true, autoUpdate: true }) 40 @column.dateTime({ autoCreate: true, autoUpdate: true })
40 public updatedAt: DateTime; 41 public updatedAt: DateTime
41} 42}