From 7584d2d7a7110aef0331ebfa178b2295842c59fa Mon Sep 17 00:00:00 2001 From: MCMXC <16797721+mcmxcdev@users.noreply.github.com> Date: Sat, 10 Feb 2024 18:19:14 -0700 Subject: refactor: project maintenance - work in progress --- .../Http/Api/Static/AnnouncementsController.ts | 20 +- app/Controllers/Http/Api/Static/EmptyController.ts | 6 +- .../Http/Api/Static/FeaturesController.ts | 6 +- .../Http/Dashboard/AccountController.ts | 40 ++-- app/Controllers/Http/Dashboard/DataController.ts | 12 +- app/Controllers/Http/Dashboard/DeleteController.ts | 14 +- app/Controllers/Http/Dashboard/ExportController.ts | 29 ++- .../Http/Dashboard/ForgotPasswordController.ts | 22 +- app/Controllers/Http/Dashboard/LogOutController.ts | 8 +- app/Controllers/Http/Dashboard/LoginController.ts | 58 +++-- .../Http/Dashboard/ResetPasswordController.ts | 51 ++--- .../Http/Dashboard/TransferController.ts | 77 +++---- app/Controllers/Http/DashboardController.ts | 2 +- app/Controllers/Http/HealthController.ts | 2 +- app/Controllers/Http/HomeController.ts | 4 +- app/Controllers/Http/RecipeController.ts | 179 +++++++-------- app/Controllers/Http/ServiceController.ts | 204 ++++++++---------- app/Controllers/Http/StaticsController.ts | 2 +- app/Controllers/Http/UserController.ts | 239 ++++++++++----------- app/Controllers/Http/WorkspaceController.ts | 98 ++++----- app/Exceptions/Handler.ts | 8 +- app/Middleware/AllowGuestOnly.ts | 33 ++- app/Middleware/Auth.ts | 61 +++--- app/Middleware/Dashboard.ts | 15 +- app/Middleware/SilentAuth.ts | 11 +- app/Models/Recipe.ts | 16 +- app/Models/Service.ts | 25 +-- app/Models/Token.ts | 27 +-- app/Models/User.ts | 85 ++++---- app/Models/Workspace.ts | 27 +-- 30 files changed, 629 insertions(+), 752 deletions(-) (limited to 'app') 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import Application from '@ioc:Adonis/Core/Application'; -import path from 'node:path'; -import fs from 'fs-extra'; +import type { HttpContext } from '@adonisjs/core/http' +import { app } from '@adonisjs/core/services/app' +import path from 'node:path' +import fs from 'fs-extra' export default class AnnouncementsController { - public async show({ response, params }: HttpContextContract) { - const announcement = path.join( - Application.resourcesPath(), - 'announcements', - `${params.version}.json`, - ); + public async show({ response, params }: HttpContext) { + const announcement = path.join(app.resourcesPath(), 'announcements', `${params.version}.json`) if (await fs.pathExists(announcement)) { - return response.download(announcement); + return response.download(announcement) } - return response.status(404).send('No announcement found.'); + return response.status(404).send('No announcement found.') } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import type { HttpContext } from '@adonisjs/core/http' export default class EmptyController { - public async show({ response }: HttpContextContract) { - return response.send([]); + public async show({ response }: HttpContext) { + return response.send([]) } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import type { HttpContext } from '@adonisjs/core/http' export default class FeaturesController { - public async show({ response }: HttpContextContract) { + public async show({ response }: HttpContext) { return response.send({ isServiceProxyEnabled: true, isWorkspaceEnabled: true, @@ -9,6 +9,6 @@ export default class FeaturesController { isSettingsWSEnabled: false, isMagicBarEnabled: true, isTodosEnabled: true, - }); + }) } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import { schema, rules, validator } from '@ioc:Adonis/Core/Validator'; -import crypto from 'node:crypto'; +import type { HttpContext } from '@adonisjs/core/http' +import { schema, rules, validator } from '@adonisjs/validator' +import crypto from 'node:crypto' export default class AccountController { /** * Shows the user account page */ - public async show({ auth, view }: HttpContextContract) { + public async show({ auth, view }: HttpContext) { return view.render('dashboard/account', { username: auth.user?.username, email: auth.user?.email, lastname: auth.user?.lastname, - }); + }) } /** * Stores user account data */ - public async store({ - auth, - request, - response, - session, - view, - }: HttpContextContract) { + public async store({ auth, request, response, session, view }: HttpContext) { try { await validator.validate({ schema: schema.create({ @@ -48,26 +42,26 @@ export default class AccountController { lastname: schema.string([rules.required()]), }), data: request.only(['username', 'email', 'lastname']), - }); + }) } catch (error) { - session.flash(error.messages); - return response.redirect('/user/account'); + session.flash(error.messages) + return response.redirect('/user/account') } // Update user account - const { user } = auth; + const { user } = auth if (user) { - user.username = request.input('username'); - user.lastname = request.input('lastname'); - user.email = request.input('email'); + user.username = request.input('username') + user.lastname = request.input('lastname') + user.email = request.input('email') if (request.input('password')) { const hashedPassword = crypto .createHash('sha256') .update(request.input('password')) - .digest('base64'); - user.password = hashedPassword; + .digest('base64') + user.password = hashedPassword } - await user.save(); + await user.save() } return view.render('dashboard/account', { @@ -75,6 +69,6 @@ export default class AccountController { lastname: user?.lastname, email: user?.email, success: user !== undefined, - }); + }) } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import type { HttpContext } from '@adonisjs/core/http' export default class DataController { /** * Display the data page */ - public async show({ view, auth }: HttpContextContract) { - const { user } = auth; + public async show({ view, auth }: HttpContext) { + const { user } = auth - const services = await user?.related('services').query(); - const workspaces = await user?.related('workspaces').query(); + const services = await user?.related('services').query() + const workspaces = await user?.related('workspaces').query() return view.render('dashboard/data', { username: user?.username, @@ -19,6 +19,6 @@ export default class DataController { stringify: JSON.stringify, services, workspaces, - }); + }) } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import type { HttpContext } from '@adonisjs/core/http' export default class DeleteController { /** * Display the delete page */ - public async show({ view }: HttpContextContract) { - return view.render('dashboard/delete'); + public async show({ view }: HttpContext) { + return view.render('dashboard/delete') } /** * Delete user and session */ - public async delete({ auth, response }: HttpContextContract) { - auth.user?.delete(); - auth.use('web').logout(); + public async delete({ auth, response }: HttpContext) { + auth.user?.delete() + auth.use('web').logout() - return response.redirect('/user/login'); + return response.redirect('/user/login') } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import type { HttpContext } from '@adonisjs/core/http' // eslint-disable-next-line @typescript-eslint/no-explicit-any function deepParseToJSON(obj: any): Record { if (typeof obj !== 'object' || obj === null) { try { // Try to parse the object as JSON - return JSON.parse(obj) as Record; + return JSON.parse(obj) as Record } catch { // If parsing fails, return the original value - return obj; + return obj } } // If obj is an object, recursively parse its keys if (Array.isArray(obj)) { // If obj is an array, recursively parse each element - return obj.map(item => deepParseToJSON(item)) as unknown as Record< - string, - unknown - >; + return obj.map((item) => deepParseToJSON(item)) as unknown as Record } else { // If obj is an object, recursively parse its keys - const parsedObj: Record = {}; + const parsedObj: Record = {} for (const key in obj) { if (obj.hasOwnProperty(key)) { - parsedObj[key] = deepParseToJSON(obj[key]); + parsedObj[key] = deepParseToJSON(obj[key]) } } - return parsedObj; + return parsedObj } } @@ -35,10 +32,10 @@ export default class ExportController { /** * Display the export page */ - public async show({ auth, response }: HttpContextContract) { - const user = auth.user!; - const services = await user.related('services').query(); - const workspaces = await user.related('workspaces').query(); + public async show({ auth, response }: HttpContext) { + const user = auth.user! + const services = await user.related('services').query() + const workspaces = await user.related('workspaces').query() const exportData = { username: user.username, @@ -46,11 +43,11 @@ export default class ExportController { mail: user.email, services: deepParseToJSON(JSON.parse(JSON.stringify(services))), workspaces: deepParseToJSON(JSON.parse(JSON.stringify(workspaces))), - }; + } return response .header('Content-Type', 'application/force-download') .header('Content-disposition', 'attachment; filename=export.ferdium-data') - .send(exportData); + .send(exportData) } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import { schema, rules, validator } from '@ioc:Adonis/Core/Validator'; -import User from 'App/Models/User'; +import type { HttpContext } from '@adonisjs/core/http' +import { schema, rules, validator } from '@adonisjs/validator' +import User from '#app/Models/User' export default class ForgotPasswordController { /** * Display the forgot password form */ - public async show({ view }: HttpContextContract) { - return view.render('dashboard/forgotPassword'); + public async show({ view }: HttpContext) { + return view.render('dashboard/forgotPassword') } /** * Send forget password email to user */ - public async forgotPassword({ view, request }: HttpContextContract) { + public async forgotPassword({ view, request }: HttpContext) { try { await validator.validate({ schema: schema.create({ mail: schema.string([rules.email(), rules.required()]), }), data: request.only(['mail']), - }); + }) } catch { return view.render('others/message', { heading: 'Cannot reset your password', text: 'Please enter a valid email address', - }); + }) } try { - const user = await User.findByOrFail('email', request.input('mail')); - await user.forgotPassword(); + const user = await User.findByOrFail('email', request.input('mail')) + await user.forgotPassword() } catch {} return view.render('others/message', { heading: 'Reset password', text: 'If your provided E-Mail address is linked to an account, we have just sent an E-Mail to that address.', - }); + }) } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import type { HttpContext } from '@adonisjs/core/http' export default class LogOutController { /** * Login a user */ - public async logout({ auth, response }: HttpContextContract) { - auth.logout(); + public async logout({ auth, response }: HttpContext) { + auth.logout() - return response.redirect('/user/login'); + return response.redirect('/user/login') } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import { schema, rules, validator } from '@ioc:Adonis/Core/Validator'; -import User from 'App/Models/User'; -import crypto from 'node:crypto'; -import { handleVerifyAndReHash } from '../../../../helpers/PasswordHash'; +import type { HttpContext } from '@adonisjs/core/http' +import { schema, rules, validator } from '@adonisjs/validator' +import User from '#app/Models/User' +import crypto from 'node:crypto' +import { handleVerifyAndReHash } from '../../../../helpers/PasswordHash.js' export default class LoginController { /** * Display the login form */ - public async show({ view }: HttpContextContract) { - return view.render('dashboard/login'); + public async show({ view }: HttpContext) { + return view.render('dashboard/login') } /** * Login a user */ - public async login({ - request, - response, - auth, - session, - }: HttpContextContract) { + public async login({ request, response, auth, session }: HttpContext) { try { await validator.validate({ schema: schema.create({ @@ -28,54 +23,51 @@ export default class LoginController { password: schema.string([rules.required()]), }), data: request.only(['mail', 'password']), - }); + }) } catch { session.flash({ type: 'danger', message: 'Invalid mail or password', - }); - session.flashExcept(['password']); + }) + session.flashExcept(['password']) - return response.redirect('/user/login'); + return response.redirect('/user/login') } try { - const { mail, password } = request.all(); + const { mail, password } = request.all() // Check if user with email exists - const user = await User.query().where('email', mail).first(); + const user = await User.query().where('email', mail).first() if (!user?.email) { - throw new Error('User credentials not valid (Invalid email)'); + throw new Error('User credentials not valid (Invalid email)') } - const hashedPassword = crypto - .createHash('sha256') - .update(password) - .digest('base64'); + const hashedPassword = crypto.createHash('sha256').update(password).digest('base64') // Verify password - let isMatchedPassword = false; + let isMatchedPassword = false try { - isMatchedPassword = await handleVerifyAndReHash(user, hashedPassword); + isMatchedPassword = await handleVerifyAndReHash(user, hashedPassword) } catch (error) { - return response.internalServerError({ message: error.message }); + return response.internalServerError({ message: error.message }) } if (!isMatchedPassword) { - throw new Error('User credentials not valid (Invalid password)'); + throw new Error('User credentials not valid (Invalid password)') } - await auth.use('web').login(user); + await auth.use('web').login(user) - return response.redirect('/user/account'); + return response.redirect('/user/account') } catch { session.flash({ type: 'danger', message: 'Invalid mail or password', - }); - session.flashExcept(['password']); + }) + session.flashExcept(['password']) - return response.redirect('/user/login'); + return response.redirect('/user/login') } } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import { schema, rules, validator } from '@ioc:Adonis/Core/Validator'; -import Token from 'App/Models/Token'; -import moment from 'moment'; -import crypto from 'node:crypto'; +import type { HttpContext } from '@adonisjs/core/http' +import { schema, rules, validator } from '@adonisjs/validator' +import Token from '#app/Models/Token' +import moment from 'moment' +import crypto from 'node:crypto' export default class ResetPasswordController { /** * Display the reset password form */ - public async show({ view, request }: HttpContextContract) { - const { token } = request.qs(); + public async show({ view, request }: HttpContext) { + const { token } = request.qs() if (token) { - return view.render('dashboard/resetPassword', { token }); + return view.render('dashboard/resetPassword', { token }) } return view.render('others/message', { heading: 'Invalid token', text: 'Please make sure you are using a valid and recent link to reset your password.', - }); + }) } /** * Resets user password */ - public async resetPassword({ - response, - request, - session, - view, - }: HttpContextContract) { + public async resetPassword({ response, request, session, view }: HttpContext) { try { await validator.validate({ schema: schema.create({ @@ -37,14 +32,14 @@ export default class ResetPasswordController { token: schema.string([rules.required()]), }), data: request.only(['password', 'password_confirmation', 'token']), - }); + }) } catch { session.flash({ type: 'danger', message: 'Passwords do not match', - }); + }) - return response.redirect(`/user/reset?token=${request.input('token')}`); + return response.redirect(`/user/reset?token=${request.input('token')}`) } const tokenRow = await Token.query() @@ -52,34 +47,30 @@ export default class ResetPasswordController { .where('token', request.input('token')) .where('type', 'forgot_password') .where('is_revoked', false) - .where( - 'updated_at', - '>=', - moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'), - ) - .first(); + .where('updated_at', '>=', moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss')) + .first() if (!tokenRow) { return view.render('others/message', { heading: 'Cannot reset your password', text: 'Please make sure you are using a valid and recent link to reset your password and that your passwords entered match.', - }); + }) } // Update user password const hashedPassword = crypto .createHash('sha256') .update(request.input('password')) - .digest('base64'); - tokenRow.user.password = hashedPassword; - await tokenRow.user.save(); + .digest('base64') + tokenRow.user.password = hashedPassword + await tokenRow.user.save() // Delete token to prevent it from being used again - await tokenRow.delete(); + await tokenRow.delete() return view.render('others/message', { heading: 'Reset password', text: 'Successfully reset your password. You can now login to your account using your new password.', - }); + }) } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import { schema, validator } from '@ioc:Adonis/Core/Validator'; -import Service from 'App/Models/Service'; -import Workspace from 'App/Models/Workspace'; -import { v4 as uuidv4 } from 'uuid'; +import type { HttpContext } from '@adonisjs/core/http' +import { schema, validator } from '@adonisjs/validator' +import Service from '#app/Models/Service' +import Workspace from '#app/Models/Workspace' +import { v4 as uuidv4 } from 'uuid' const importSchema = schema.create({ username: schema.string(), @@ -10,58 +10,52 @@ const importSchema = schema.create({ mail: schema.string(), services: schema.array().anyMembers(), workspaces: schema.array().anyMembers(), -}); +}) export default class TransferController { /** * Display the transfer page */ - public async show({ view }: HttpContextContract) { - return view.render('dashboard/transfer'); + public async show({ view }: HttpContext) { + return view.render('dashboard/transfer') } - public async import({ - auth, - request, - response, - session, - view, - }: HttpContextContract) { - let file; + public async import({ auth, request, response, session, view }: HttpContext) { + let file try { file = await validator.validate({ schema: importSchema, data: JSON.parse(request.body().file), - }); + }) } catch { session.flash({ message: 'Invalid Ferdium account file', - }); + }) - return response.redirect('/user/transfer'); + return response.redirect('/user/transfer') } if (!file?.services || !file.workspaces) { session.flash({ type: 'danger', message: 'Invalid Ferdium account file (2)', - }); - return response.redirect('/user/transfer'); + }) + return response.redirect('/user/transfer') } - const serviceIdTranslation = {}; + const serviceIdTranslation = {} // Import services try { for (const service of file.services) { // Get new, unused uuid - let serviceId; + let serviceId do { - serviceId = uuidv4(); + serviceId = uuidv4() } while ( // eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member (await Service.query().where('serviceId', serviceId)).length > 0 - ); + ) // eslint-disable-next-line no-await-in-loop await Service.create({ @@ -73,38 +67,37 @@ export default class TransferController { typeof service.settings === 'string' ? service.settings : JSON.stringify(service.settings), - }); + }) // @ts-expect-error Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{}' - serviceIdTranslation[service.service_id || service.serviceId] = - serviceId; + serviceIdTranslation[service.service_id || service.serviceId] = serviceId } } catch (error) { // eslint-disable-next-line no-console - console.log(error); - const errorMessage = `Could not import your services into our system.\nError: ${error}`; + console.log(error) + const errorMessage = `Could not import your services into our system.\nError: ${error}` return view.render('others/message', { heading: 'Error while importing', text: errorMessage, - }); + }) } // Import workspaces try { for (const workspace of file.workspaces) { - let workspaceId; + let workspaceId do { - workspaceId = uuidv4(); + workspaceId = uuidv4() } while ( // eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member (await Workspace.query().where('workspaceId', workspaceId)).length > 0 - ); + ) const services = workspace.services.map( // @ts-expect-error Parameter 'service' implicitly has an 'any' type. - service => serviceIdTranslation[service], - ); + (service) => serviceIdTranslation[service] + ) // eslint-disable-next-line no-await-in-loop await Workspace.create({ @@ -114,22 +107,20 @@ export default class TransferController { order: workspace.order, services: JSON.stringify(services), data: - typeof workspace.data === 'string' - ? workspace.data - : JSON.stringify(workspace.data), - }); + typeof workspace.data === 'string' ? workspace.data : JSON.stringify(workspace.data), + }) } } catch (error) { - const errorMessage = `Could not import your workspaces into our system.\nError: ${error}`; + const errorMessage = `Could not import your workspaces into our system.\nError: ${error}` return view.render('others/message', { heading: 'Error while importing', text: errorMessage, - }); + }) } return view.render('others/message', { heading: 'Successfully imported', text: 'Your account has been imported, you can now login as usual!', - }); + }) } } 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 @@ -// import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +// import type { HttpContext } from '@adonisjs/core/http'; export default class DashboardController {} 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 { return { api: 'success', db: 'success', - }; + } } } 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 @@ -// import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +// import type { HttpContext } from '@adonisjs/core/http' export default class HomeController { public async index() { // TODO: Actually do something instead of alwayas returning success. - return { hello: 'world' }; + return { hello: 'world' } } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import fs from 'fs-extra'; -import Application from '@ioc:Adonis/Core/Application'; -import path from 'node:path'; -import Recipe from 'App/Models/Recipe'; -import { isCreationEnabled } from 'Config/app'; -import { validator, schema, rules } from '@ioc:Adonis/Core/Validator'; -import targz from 'targz'; -import semver from 'semver'; -import Drive from '@ioc:Adonis/Core/Drive'; +import type { HttpContext } from '@adonisjs/core/http' +import fs from 'fs-extra' +import { app } from '@adonisjs/core/services/app' +import path from 'node:path' +import Recipe from '#app/Models/Recipe' +import { isCreationEnabled } from '#config/app' +import { validator, schema, rules } from '@adonisjs/validator' +import targz from 'targz' +import semver from 'semver' +import Drive from '@ioc:Adonis/Core/Drive' // TODO: This file needs to be refactored and cleaned up to include types @@ -18,17 +18,17 @@ const createSchema = schema.create({ // author: 'required|accepted', author: schema.string(), svg: schema.string([rules.url()]), -}); +}) const searchSchema = schema.create({ needle: schema.string(), -}); +}) const downloadSchema = schema.create({ // TODO: Check if this is correct // recipe: 'required|accepted', recipe: schema.string(), -}); +}) const compress = (src: string, dest: string) => new Promise((resolve, reject) => { @@ -37,87 +37,76 @@ const compress = (src: string, dest: string) => src, dest, }, - err => { + (err) => { if (err) { - reject(err); + reject(err) } else { - resolve(dest); + resolve(dest) } - }, - ); - }); + } + ) + }) export default class RecipesController { // List official and custom recipes - public async list({ response }: HttpContextContract) { - const officialRecipes = fs.readJsonSync( - path.join(Application.appRoot, 'recipes', 'all.json'), - ); - const customRecipesArray = await Recipe.all(); - const customRecipes = customRecipesArray.map(recipe => ({ + public async list({ response }: HttpContext) { + const officialRecipes = fs.readJsonSync(path.join(app.appRoot, 'recipes', 'all.json')) + const customRecipesArray = await Recipe.all() + const customRecipes = customRecipesArray.map((recipe) => ({ id: recipe.recipeId, name: recipe.name, - ...(typeof recipe.data === 'string' - ? JSON.parse(recipe.data) - : recipe.data), - })); + ...(typeof recipe.data === 'string' ? JSON.parse(recipe.data) : recipe.data), + })) - const recipes = [...officialRecipes, ...customRecipes]; + const recipes = [...officialRecipes, ...customRecipes] - return response.send(recipes); + return response.send(recipes) } // TODO: Test this endpoint // Create a new recipe using the new.html page - public async create({ request, response }: HttpContextContract) { + public async create({ request, response }: HttpContext) { // Check if recipe creation is enabled if (isCreationEnabled === 'false') { - return response.send( - 'This server doesn\'t allow the creation of new recipes.', - ); + return response.send("This server doesn't allow the creation of new recipes.") } // Validate user input - let data; + let data try { - data = await request.validate({ schema: createSchema }); + data = await request.validate({ schema: createSchema }) } catch (error) { return response.status(401).send({ message: 'Invalid POST arguments', messages: error.messages, status: 401, - }); + }) } if (!data.id) { - return response.send('Please provide an ID'); + return response.send('Please provide an ID') } // Check for invalid characters if (/\.+/.test(data.id) || /\/+/.test(data.id)) { - return response.send( - 'Invalid recipe name. Your recipe name may not contain "." or "/"', - ); + return response.send('Invalid recipe name. Your recipe name may not contain "." or "/"') } // Clear temporary recipe folder - await fs.emptyDir(Application.tmpPath('recipe')); + await fs.emptyDir(app.tmpPath('recipe')) // Move uploaded files to temporary path - const files = request.file('files'); + const files = request.file('files') if (!files) { - return response.abort('Error processsing files.'); + return response.abort('Error processsing files.') } - await files.move(Application.tmpPath('recipe')); + await files.move(app.tmpPath('recipe')) // Compress files to .tar.gz file - const source = Application.tmpPath('recipe'); - const destination = path.join( - Application.appRoot, - `/recipes/archives/${data.id}.tar.gz`, - ); + const source = app.tmpPath('recipe') + const destination = path.join(app.appRoot, `/recipes/archives/${data.id}.tar.gz`) - compress(source, destination); + compress(source, destination) // Create recipe in db await Recipe.create({ @@ -132,123 +121,111 @@ export default class RecipesController { svg: data.svg, }, }), - }); + }) - return response.send('Created new recipe'); + return response.send('Created new recipe') } // Search official and custom recipes - public async search({ request, response }: HttpContextContract) { + public async search({ request, response }: HttpContext) { // Validate user input - let data; + let data try { - data = await request.validate({ schema: searchSchema }); + data = await request.validate({ schema: searchSchema }) } catch (error) { return response.status(401).send({ message: 'Please provide a needle', messages: error.messages, status: 401, - }); + }) } - const { needle } = data; + const { needle } = data // Get results - let results; + let results if (needle === 'ferdium:custom') { - const dbResults = await Recipe.all(); - results = dbResults.map(recipe => ({ + const dbResults = await Recipe.all() + results = dbResults.map((recipe) => ({ id: recipe.recipeId, name: recipe.name, - ...(typeof recipe.data === 'string' - ? JSON.parse(recipe.data) - : recipe.data), - })); + ...(typeof recipe.data === 'string' ? JSON.parse(recipe.data) : recipe.data), + })) } else { - const localResultsArray = await Recipe.query().where( - 'name', - 'LIKE', - `%${needle}%`, - ); - results = localResultsArray.map(recipe => ({ + const localResultsArray = await Recipe.query().where('name', 'LIKE', `%${needle}%`) + results = localResultsArray.map((recipe) => ({ id: recipe.recipeId, name: recipe.name, - ...(typeof recipe.data === 'string' - ? JSON.parse(recipe.data) - : recipe.data), - })); + ...(typeof recipe.data === 'string' ? JSON.parse(recipe.data) : recipe.data), + })) } - return response.send(results); + return response.send(results) } - public popularRecipes({ response }: HttpContextContract) { + public popularRecipes({ response }: HttpContext) { return response.send( fs - .readJsonSync(path.join(Application.appRoot, 'recipes', 'all.json')) + .readJsonSync(path.join(app.appRoot, 'recipes', 'all.json')) // eslint-disable-next-line @typescript-eslint/no-explicit-any - .filter((recipe: any) => recipe.featured), - ); + .filter((recipe: any) => recipe.featured) + ) } // TODO: test this endpoint - public update({ request, response }: HttpContextContract) { - const updates = []; - const recipes = request.all(); - const allJson = fs.readJsonSync( - path.join(Application.appRoot, 'recipes', 'all.json'), - ); + public update({ request, response }: HttpContext) { + const updates = [] + const recipes = request.all() + const allJson = fs.readJsonSync(path.join(app.appRoot, 'recipes', 'all.json')) for (const recipe of Object.keys(recipes)) { - const version = recipes[recipe]; + const version = recipes[recipe] // Find recipe in local recipe repository // eslint-disable-next-line @typescript-eslint/no-explicit-any - const localRecipe = allJson.find((r: any) => r.id === recipe); + const localRecipe = allJson.find((r: any) => r.id === recipe) if (localRecipe && semver.lt(version, localRecipe.version)) { - updates.push(recipe); + updates.push(recipe) } } - return response.send(updates); + return response.send(updates) } // TODO: test this endpoint // Download a recipe - public async download({ response, params }: HttpContextContract) { + public async download({ response, params }: HttpContext) { // Validate user input - let data; + let data try { data = await validator.validate({ data: params, schema: downloadSchema, - }); + }) } catch (error) { return response.status(401).send({ message: 'Please provide a recipe ID', messages: error.messages, status: 401, - }); + }) } - const service = data.recipe; + const service = data.recipe // Check for invalid characters if (/\.+/.test(service) || /\/+/.test(service)) { - return response.send('Invalid recipe name'); + return response.send('Invalid recipe name') } // Check if recipe exists in recipes folder if (await Drive.exists(`${service}.tar.gz`)) { - return response - .type('.tar.gz') - .send(await Drive.get(`${service}.tar.gz`)); + return response.type('.tar.gz').send(await Drive.get(`${service}.tar.gz`)) } return response.status(400).send({ message: 'Recipe not found', code: 'recipe-not-found', - }); + }) } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import { schema } from '@ioc:Adonis/Core/Validator'; -import Service from 'App/Models/Service'; -import { url } from 'Config/app'; -import { v4 as uuid } from 'uuid'; -import * as fs from 'fs-extra'; -import path from 'node:path'; -import Application from '@ioc:Adonis/Core/Application'; -import sanitize from 'sanitize-filename'; +import type { HttpContext } from '@adonisjs/core/http' +import { schema } from '@adonisjs/validator' +import Service from '#app/Models/Service' +import { url } from '#config/app' +import { v4 as uuid } from 'uuid' +import * as fs from 'fs-extra' +import path from 'node:path' +import { app } from '@adonisjs/core/services/app' +import sanitize from 'sanitize-filename' const createSchema = schema.create({ name: schema.string(), recipeId: schema.string(), -}); +}) export default class ServiceController { // Create a new service for user - public async create({ request, response, auth }: HttpContextContract) { + public async create({ request, response, auth }: HttpContext) { // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. - const user = auth.user ?? request.user; + const user = auth.user ?? request.user if (!user) { - return response.unauthorized('Missing or invalid api token'); + return response.unauthorized('Missing or invalid api token') } // Validate user input - const data = request.all(); + const data = request.all() try { - await request.validate({ schema: createSchema }); + await request.validate({ schema: createSchema }) } catch (error) { return response.status(401).send({ message: 'Invalid POST arguments', messages: error.messages, status: 401, - }); + }) } // Get new, unused uuid - let serviceId; + let serviceId do { - serviceId = uuid(); + serviceId = uuid() } while ( // eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member (await Service.query().where('serviceId', serviceId)).length > 0 - ); + ) await Service.create({ userId: user.id, @@ -51,7 +51,7 @@ export default class ServiceController { name: data.name, recipeId: data.recipeId, settings: JSON.stringify(data), - }); + }) return response.send({ data: { @@ -72,28 +72,26 @@ export default class ServiceController { ...data, }, status: ['created'], - }); + }) } // List all services a user has created - public async list({ request, response, auth }: HttpContextContract) { + public async list({ request, response, auth }: HttpContext) { // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. - const user = auth.user ?? request.user; + const user = auth.user ?? request.user if (!user) { - return response.unauthorized('Missing or invalid api token'); + return response.unauthorized('Missing or invalid api token') } - const { id } = user; - const services = await user.related('services').query(); + const { id } = user + const services = await user.related('services').query() // Convert to array with all data Franz wants // eslint-disable-next-line @typescript-eslint/no-explicit-any const servicesArray = services.map((service: any) => { const settings = - typeof service.settings === 'string' - ? JSON.parse(service.settings) - : service.settings; + typeof service.settings === 'string' ? JSON.parse(service.settings) : service.settings return { customRecipe: false, @@ -110,99 +108,87 @@ export default class ServiceController { iconUrl: settings.iconId ? `${url}/v1/icon/${settings.iconId}` : // eslint-disable-next-line unicorn/no-null - null, + null, id: service.serviceId, name: service.name, recipeId: service.recipeId, userId: id, - }; - }); + } + }) - return response.send(servicesArray); + return response.send(servicesArray) } - public async delete({ - request, - params, - auth, - response, - }: HttpContextContract) { + public async delete({ request, params, auth, response }: HttpContext) { // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. - const user = auth.user ?? request.user; + const user = auth.user ?? request.user if (!user) { - return response.unauthorized('Missing or invalid api token'); + return response.unauthorized('Missing or invalid api token') } // Update data in database - await Service.query() - .where('serviceId', params.id) - .where('userId', user.id) - .delete(); + await Service.query().where('serviceId', params.id).where('userId', user.id).delete() return response.send({ message: 'Sucessfully deleted service', status: 200, - }); + }) } // TODO: Test if icon upload works - public async edit({ request, response, auth, params }: HttpContextContract) { + public async edit({ request, response, auth, params }: HttpContext) { // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. - const user = auth.user ?? request.user; + const user = auth.user ?? request.user if (!user) { - return response.unauthorized('Missing or invalid api token'); + return response.unauthorized('Missing or invalid api token') } - const { id } = params; + const { id } = params const service = await Service.query() .where('serviceId', id) .where('userId', user.id) - .firstOrFail(); + .firstOrFail() if (request.file('icon')) { // Upload custom service icon const icon = request.file('icon', { extnames: ['png', 'jpg', 'jpeg', 'svg'], size: '2mb', - }); + }) if (icon === null) { - return response.badRequest('Icon not uploaded.'); + return response.badRequest('Icon not uploaded.') } const settings = - typeof service.settings === 'string' - ? JSON.parse(service.settings) - : service.settings; + typeof service.settings === 'string' ? JSON.parse(service.settings) : service.settings - let iconId; + let iconId do { - iconId = uuid() + uuid(); + iconId = uuid() + uuid() } while ( // eslint-disable-next-line no-await-in-loop - await fs.exists(path.join(Application.tmpPath('uploads'), iconId)) - ); - iconId = `${iconId}.${icon.extname}`; + await fs.exists(path.join(app.tmpPath('uploads'), iconId)) + ) + iconId = `${iconId}.${icon.extname}` - await icon.move(Application.tmpPath('uploads'), { + await icon.move(app.tmpPath('uploads'), { name: iconId, overwrite: true, - }); + }) if (icon.state !== 'moved') { - return response.status(500).send(icon.errors); + return response.status(500).send(icon.errors) } const newSettings = { ...settings, iconId, - customIconVersion: settings?.customIconVersion - ? settings.customIconVersion + 1 - : 1, - }; + customIconVersion: settings?.customIconVersion ? settings.customIconVersion + 1 : 1, + } // Update data in database await Service.query() @@ -211,7 +197,7 @@ export default class ServiceController { .update({ name: service.name, settings: JSON.stringify(newSettings), - }); + }) return response.send({ data: { @@ -222,28 +208,24 @@ export default class ServiceController { userId: user.id, }, status: ['updated'], - }); + }) } // Update service info - const data = request.all(); + const data = request.all() const settings = { - ...(typeof service.settings === 'string' - ? JSON.parse(service.settings) - : service.settings), + ...(typeof service.settings === 'string' ? JSON.parse(service.settings) : service.settings), ...data, - }; + } if (settings.customIcon === 'delete') { - fs.remove( - path.join(Application.tmpPath('uploads'), settings.iconId), - ).catch(error => { - console.error(error); - }); - - settings.iconId = undefined; - settings.customIconVersion = undefined; - settings.customIcon = ''; + fs.remove(path.join(app.tmpPath('uploads'), settings.iconId)).catch((error) => { + console.error(error) + }) + + settings.iconId = undefined + settings.customIconVersion = undefined + settings.customIcon = '' } // Update data in database @@ -253,13 +235,13 @@ export default class ServiceController { .update({ name: data.name, settings: JSON.stringify(settings), - }); + }) // Get updated row const serviceUpdated = await Service.query() .where('serviceId', id) .where('userId', user.id) - .firstOrFail(); + .firstOrFail() return response.send({ data: { @@ -270,19 +252,19 @@ export default class ServiceController { userId: user.id, }, status: ['updated'], - }); + }) } // TODO: Test if this works - public async reorder({ request, response, auth }: HttpContextContract) { + public async reorder({ request, response, auth }: HttpContext) { // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. - const user = auth.user ?? request.user; + const user = auth.user ?? request.user if (!user) { - return response.unauthorized('Missing or invalid api token'); + return response.unauthorized('Missing or invalid api token') } - const data = request.all(); + const data = request.all() for (const service of Object.keys(data)) { // Get current settings from db @@ -290,14 +272,14 @@ export default class ServiceController { .where('serviceId', service) .where('userId', user.id) - .firstOrFail(); + .firstOrFail() const settings = { ...(typeof serviceData.settings === 'string' ? JSON.parse(serviceData.settings) : serviceData.settings), order: data[service], - }; + } // Update data in database await Service.query() // eslint-disable-line no-await-in-loop @@ -305,18 +287,16 @@ export default class ServiceController { .where('userId', user.id) .update({ settings: JSON.stringify(settings), - }); + }) } // Get new services - const services = await user.related('services').query(); + const services = await user.related('services').query() // Convert to array with all data Franz wants // eslint-disable-next-line @typescript-eslint/no-explicit-any const servicesArray = services.map((service: any) => { const settings = - typeof service.settings === 'string' - ? JSON.parse(service.settings) - : service.settings; + typeof service.settings === 'string' ? JSON.parse(service.settings) : service.settings return { customRecipe: false, @@ -333,39 +313,39 @@ export default class ServiceController { iconUrl: settings.iconId ? `${url}/v1/icon/${settings.iconId}` : // eslint-disable-next-line unicorn/no-null - null, + null, id: service.serviceId, name: service.name, recipeId: service.recipeId, userId: user.id, - }; - }); + } + }) - return response.send(servicesArray); + return response.send(servicesArray) } // TODO: Test if this works - public async icon({ params, response }: HttpContextContract) { - let { id } = params; + public async icon({ params, response }: HttpContext) { + let { id } = params - id = sanitize(id); + id = sanitize(id) if (id === '') { return response.status(404).send({ - status: 'Icon doesn\'t exist', - }); + status: "Icon doesn't exist", + }) } - const iconPath = path.join(Application.tmpPath('uploads'), id); + const iconPath = path.join(app.tmpPath('uploads'), id) try { - await fs.access(iconPath); + await fs.access(iconPath) } catch { // File not available. return response.status(404).send({ - status: 'Icon doesn\'t exist', - }); + status: "Icon doesn't exist", + }) } - return response.download(iconPath); + return response.download(iconPath) } } 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 @@ -// import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' +// import type { HttpContext } from '@adonisjs/core/http' export default class StaticsController {} diff --git a/app/Controllers/Http/UserController.ts b/app/Controllers/Http/UserController.ts index ef7cfdd..088f7b1 100644 --- a/app/Controllers/Http/UserController.ts +++ b/app/Controllers/Http/UserController.ts @@ -1,134 +1,125 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import { schema, rules } from '@ioc:Adonis/Core/Validator'; -import User from 'App/Models/User'; -import { connectWithFranz, isRegistrationEnabled } from '../../../config/app'; -import crypto from 'node:crypto'; -import { v4 as uuid } from 'uuid'; -import Workspace from 'App/Models/Workspace'; -import Service from 'App/Models/Service'; -import fetch from 'node-fetch'; +import type { HttpContext } from '@adonisjs/core/http' +import { schema, rules } from '@adonisjs/validator' +import User from '#app/Models/User' +import { connectWithFranz, isRegistrationEnabled } from '../../../config/app.js' +import crypto from 'node:crypto' +import { v4 as uuid } from 'uuid' +import Workspace from '#app/Models/Workspace' +import Service from '#app/Models/Service' // TODO: This file needs to be refactored and cleaned up to include types -import { handleVerifyAndReHash } from '../../../helpers/PasswordHash'; +import { handleVerifyAndReHash } from '../../../helpers/PasswordHash.js' const newPostSchema = schema.create({ firstname: schema.string(), lastname: schema.string(), - email: schema.string([ - rules.email(), - rules.unique({ table: 'users', column: 'email' }), - ]), + email: schema.string([rules.email(), rules.unique({ table: 'users', column: 'email' })]), password: schema.string([rules.minLength(8)]), -}); +}) const franzImportSchema = schema.create({ - email: schema.string([ - rules.email(), - rules.unique({ table: 'users', column: 'email' }), - ]), + email: schema.string([rules.email(), rules.unique({ table: 'users', column: 'email' })]), password: schema.string([rules.minLength(8)]), -}); +}) // // TODO: This whole controller needs to be changed such that it can support importing from both Franz and Ferdi // eslint-disable-next-line @typescript-eslint/no-explicit-any const franzRequest = (route: any, method: any, auth: any) => new Promise((resolve, reject) => { - const base = 'https://api.franzinfra.com/v1/'; + const base = 'https://api.franzinfra.com/v1/' const user = - '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'; + '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' try { fetch(base + route, { method, headers: { - Authorization: `Bearer ${auth}`, + 'Authorization': `Bearer ${auth}`, 'User-Agent': user, }, }) - .then(data => data.json()) - .then(json => resolve(json)); + .then((data) => data.json()) + .then((json) => resolve(json)) } catch { - reject(); + reject() } - }); + }) export default class UsersController { // Register a new user - public async signup({ request, response, auth }: HttpContextContract) { + public async signup({ request, response, auth }: HttpContext) { if (isRegistrationEnabled === 'false') { return response.status(401).send({ message: 'Registration is disabled on this server', status: 401, - }); + }) } // Validate user input - let data; + let data try { - data = await request.validate({ schema: newPostSchema }); + data = await request.validate({ schema: newPostSchema }) } catch (error) { return response.status(401).send({ message: 'Invalid POST arguments', messages: error.messages, status: 401, - }); + }) } // Create user in DB - let user; + let user try { user = await User.create({ email: data.email, password: data.password, username: data.firstname, lastname: data.lastname, - }); + }) } catch { return response.status(401).send({ message: 'E-Mail address already in use', status: 401, - }); + }) } // Generate new auth token - const token = await auth.use('jwt').login(user, { payload: {} }); + const token = await auth.use('jwt').login(user, { payload: {} }) return response.send({ message: 'Successfully created account', token: token.accessToken, - }); + }) } // Login using an existing user - public async login({ request, response, auth }: HttpContextContract) { + public async login({ request, response, auth }: HttpContext) { if (!request.header('Authorization')) { return response.status(401).send({ message: 'Please provide authorization', status: 401, - }); + }) } // Get auth data from auth token - const authHeader = atob( - request.header('Authorization')!.replace('Basic ', ''), - ).split(':'); + const authHeader = atob(request.header('Authorization')!.replace('Basic ', '')).split(':') // Check if user with email exists - const user = await User.query().where('email', authHeader[0]).first(); + const user = await User.query().where('email', authHeader[0]).first() if (!user?.email) { return response.status(401).send({ message: 'User credentials not valid', code: 'invalid-credentials', status: 401, - }); + }) } // Verify password - let isMatchedPassword = false; + let isMatchedPassword = false try { - isMatchedPassword = await handleVerifyAndReHash(user, authHeader[1]); + isMatchedPassword = await handleVerifyAndReHash(user, authHeader[1]) } catch (error) { - return response.internalServerError({ message: error.message }); + return response.internalServerError({ message: error.message }) } if (!isMatchedPassword) { @@ -136,31 +127,28 @@ export default class UsersController { message: 'User credentials not valid', code: 'invalid-credentials', status: 401, - }); + }) } // Generate token - const token = await auth.use('jwt').login(user, { payload: {} }); + const token = await auth.use('jwt').login(user, { payload: {} }) return response.send({ message: 'Successfully logged in', token: token.accessToken, - }); + }) } // Return information about the current user - public async me({ request, response, auth }: HttpContextContract) { + public async me({ request, response, auth }: HttpContext) { // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. - const user = auth.user ?? request.user; + const user = auth.user ?? request.user if (!user) { - return response.send('Missing or invalid api token'); + return response.send('Missing or invalid api token') } - const settings = - typeof user.settings === 'string' - ? JSON.parse(user.settings) - : user.settings; + const settings = typeof user.settings === 'string' ? JSON.parse(user.settings) : user.settings return response.send({ accountType: 'individual', @@ -176,29 +164,29 @@ export default class UsersController { lastname: user.lastname, locale: 'en-US', ...settings, - }); + }) } - public async updateMe({ request, response, auth }: HttpContextContract) { + public async updateMe({ request, response, auth }: HttpContext) { // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. - const user = auth.user ?? request.user; + const user = auth.user ?? request.user if (!user) { - return response.send('Missing or invalid api token'); + return response.send('Missing or invalid api token') } - let settings = user.settings || {}; + let settings = user.settings || {} if (typeof settings === 'string') { - settings = JSON.parse(settings); + settings = JSON.parse(settings) } const newSettings = { ...settings, ...request.all(), - }; + } - user.settings = JSON.stringify(newSettings); - await user.save(); + user.settings = JSON.stringify(newSettings) + await user.save() return response.send({ data: { @@ -217,140 +205,137 @@ export default class UsersController { ...newSettings, }, status: ['data-updated'], - }); + }) } - public async newToken({ request, response, auth }: HttpContextContract) { + public async newToken({ request, response, auth }: HttpContext) { // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. - const user = auth.user ?? request.user; + const user = auth.user ?? request.user if (!user) { - return response.send('Missing or invalid api token'); + return response.send('Missing or invalid api token') } - const token = await auth.use('jwt').generate(user, { payload: {} }); + const token = await auth.use('jwt').generate(user, { payload: {} }) return response.send({ token: token.accessToken, - }); + }) } - public async import({ request, response, view }: HttpContextContract) { + public async import({ request, response, view }: HttpContext) { if (isRegistrationEnabled === 'false') { return response.status(401).send({ message: 'Registration is disabled on this server', status: 401, - }); + }) } if (connectWithFranz === 'false') { return response.send( - '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.', - ); + '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.' + ) } // Validate user input - let data; + let data try { - data = await request.validate({ schema: franzImportSchema }); + data = await request.validate({ schema: franzImportSchema }) } catch (error) { return view.render('others.message', { heading: 'Error while importing', text: error.messages, - }); + }) } - const { email, password } = data; + const { email, password } = data - const hashedPassword = crypto - .createHash('sha256') - .update(password) - .digest('base64'); + const hashedPassword = crypto.createHash('sha256').update(password).digest('base64') - const base = 'https://api.franzinfra.com/v1/'; + const base = 'https://api.franzinfra.com/v1/' const userAgent = - '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'; + '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' // Try to get an authentication token - let token; + let token try { - const basicToken = btoa(`${email}:${hashedPassword}`); + const basicToken = btoa(`${email}:${hashedPassword}`) const loginBody = { isZendeskLogin: false, - }; + } const rawResponse = await fetch(`${base}auth/login`, { method: 'POST', body: JSON.stringify(loginBody), headers: { - Authorization: `Basic ${basicToken}`, + 'Authorization': `Basic ${basicToken}`, 'User-Agent': userAgent, 'Content-Type': 'application/json', - accept: '*/*', + 'accept': '*/*', 'x-franz-source': 'Web', }, - }); - const content = await rawResponse.json(); + }) + const content = await rawResponse.json() if (!content.message || content.message !== 'Successfully logged in') { const errorMessage = - 'Could not login into Franz with your supplied credentials. Please check and try again'; - return response.status(401).send(errorMessage); + 'Could not login into Franz with your supplied credentials. Please check and try again' + return response.status(401).send(errorMessage) } - token = content.token; + token = content.token } catch (error) { return response.status(401).send({ message: 'Cannot login to Franz', error: error, - }); + }) } // Get user information // eslint-disable-next-line @typescript-eslint/no-explicit-any - let userInf: any = false; + let userInf: any = false try { - userInf = await franzRequest('me', 'GET', token); + userInf = await franzRequest('me', 'GET', token) } catch (error) { - const errorMessage = `Could not get your user info from Franz. Please check your credentials or try again later.\nError: ${error}`; - return response.status(401).send(errorMessage); + const errorMessage = `Could not get your user info from Franz. Please check your credentials or try again later.\nError: ${error}` + return response.status(401).send(errorMessage) } if (!userInf) { const errorMessage = - 'Could not get your user info from Franz. Please check your credentials or try again later'; - return response.status(401).send(errorMessage); + 'Could not get your user info from Franz. Please check your credentials or try again later' + return response.status(401).send(errorMessage) } // Create user in DB - let user; + let user try { user = await User.create({ email: userInf.email, password: hashedPassword, username: userInf.firstname, lastname: userInf.lastname, - }); + }) } catch (error) { - const errorMessage = `Could not create your user in our system.\nError: ${error}`; - return response.status(401).send(errorMessage); + const errorMessage = `Could not create your user in our system.\nError: ${error}` + return response.status(401).send(errorMessage) } - const serviceIdTranslation = {}; + const serviceIdTranslation = {} // Import services try { - const services = await franzRequest('me/services', 'GET', token); + const services = await franzRequest('me/services', 'GET', token) // @ts-expect-error for (const service of services) { // Get new, unused uuid - let serviceId; + let serviceId do { - serviceId = uuid(); + serviceId = uuid() } while ( // eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member (await Service.query().where('serviceId', serviceId)).length > 0 - ); + ) // eslint-disable-next-line no-await-in-loop await Service.create({ @@ -359,34 +344,34 @@ export default class UsersController { name: service.name, recipeId: service.recipeId, settings: JSON.stringify(service), - }); + }) // @ts-expect-error - serviceIdTranslation[service.id] = serviceId; + serviceIdTranslation[service.id] = serviceId } } catch (error) { - const errorMessage = `Could not import your services into our system.\nError: ${error}`; - return response.status(401).send(errorMessage); + const errorMessage = `Could not import your services into our system.\nError: ${error}` + return response.status(401).send(errorMessage) } // Import workspaces try { - const workspaces = await franzRequest('workspace', 'GET', token); + const workspaces = await franzRequest('workspace', 'GET', token) // @ts-expect-error for (const workspace of workspaces) { - let workspaceId; + let workspaceId do { - workspaceId = uuid(); + workspaceId = uuid() } while ( // eslint-disable-next-line unicorn/no-await-expression-member, no-await-in-loop (await Workspace.query().where('workspaceId', workspaceId)).length > 0 - ); + ) const services = workspace.services.map( // @ts-expect-error - service => serviceIdTranslation[service], - ); + (service) => serviceIdTranslation[service] + ) // eslint-disable-next-line no-await-in-loop await Workspace.create({ @@ -396,15 +381,15 @@ export default class UsersController { order: workspace.order, services: JSON.stringify(services), data: JSON.stringify({}), - }); + }) } } catch (error) { - const errorMessage = `Could not import your workspaces into our system.\nError: ${error}`; - return response.status(401).send(errorMessage); + const errorMessage = `Could not import your workspaces into our system.\nError: ${error}` + return response.status(401).send(errorMessage) } return response.send( - 'Your account has been imported. You can now use your Franz/Ferdi account in Ferdium.', - ); + 'Your account has been imported. You can now use your Franz/Ferdi account in Ferdium.' + ) } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import { validator, schema } from '@ioc:Adonis/Core/Validator'; -import Workspace from 'App/Models/Workspace'; -import { v4 as uuid } from 'uuid'; +import type { HttpContext } from '@adonisjs/core/http' +import { validator, schema } from '@adonisjs/validator' +import Workspace from '#app/Models/Workspace' +import { v4 as uuid } from 'uuid' const createSchema = schema.create({ name: schema.string(), -}); +}) const editSchema = schema.create({ name: schema.string(), -}); +}) const deleteSchema = schema.create({ id: schema.string(), -}); +}) export default class WorkspaceController { // Create a new workspace for user - public async create({ request, response, auth }: HttpContextContract) { + public async create({ request, response, auth }: HttpContext) { // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. - const user = auth.user ?? request.user; + const user = auth.user ?? request.user if (!user) { - return response.unauthorized('Missing or invalid api token'); + return response.unauthorized('Missing or invalid api token') } // Validate user input - let data; + let data try { - data = await request.validate({ schema: createSchema }); + data = await request.validate({ schema: createSchema }) } catch (error) { return response.status(401).send({ message: 'Invalid POST arguments', messages: error.messages, status: 401, - }); + }) } // Get new, unused uuid - let workspaceId; + let workspaceId do { - workspaceId = uuid(); + workspaceId = uuid() } while ( // eslint-disable-next-line unicorn/no-await-expression-member, no-await-in-loop (await Workspace.query().where('workspaceId', workspaceId)).length > 0 - ); + ) // eslint-disable-next-line unicorn/no-await-expression-member - const order = (await user.related('workspaces').query()).length; + const order = (await user.related('workspaces').query()).length await Workspace.create({ userId: user.id, @@ -56,7 +56,7 @@ export default class WorkspaceController { order, services: JSON.stringify([]), data: JSON.stringify(data), - }); + }) return response.send({ userId: user.id, @@ -64,30 +64,30 @@ export default class WorkspaceController { id: workspaceId, order, workspaces: [], - }); + }) } - public async edit({ request, response, auth, params }: HttpContextContract) { + public async edit({ request, response, auth, params }: HttpContext) { // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. - const user = auth.user ?? request.user; + const user = auth.user ?? request.user if (!user) { - return response.unauthorized('Missing or invalid api token'); + return response.unauthorized('Missing or invalid api token') } // Validate user input try { - await request.validate({ schema: editSchema }); + await request.validate({ schema: editSchema }) } catch (error) { return response.status(401).send({ message: 'Invalid POST arguments', messages: error.messages, status: 401, - }); + }) } - const data = request.all(); - const { id } = params; + const data = request.all() + const { id } = params // Update data in database await Workspace.query() @@ -96,13 +96,13 @@ export default class WorkspaceController { .update({ name: data.name, services: JSON.stringify(data.services), - }); + }) // Get updated row const workspace = await Workspace.query() .where('workspaceId', id) .where('userId', user.id) - .firstOrFail(); + .firstOrFail() return response.send({ id: workspace.workspaceId, @@ -110,62 +110,54 @@ export default class WorkspaceController { order: workspace.order, services: data.services, userId: user.id, - }); + }) } - public async delete({ - request, - response, - auth, - params, - }: HttpContextContract) { + public async delete({ request, response, auth, params }: HttpContext) { // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. - const user = auth.user ?? request.user; + const user = auth.user ?? request.user if (!user) { - return response.unauthorized('Missing or invalid api token'); + return response.unauthorized('Missing or invalid api token') } // Validate user input - let data; + let data try { data = await validator.validate({ data: params, schema: deleteSchema, - }); + }) } catch (error) { return response.status(401).send({ message: 'Invalid arguments', messages: error.messages, status: 401, - }); + }) } - const { id } = data; + const { id } = data // Update data in database - await Workspace.query() - .where('workspaceId', id) - .where('userId', user.id) - .delete(); + await Workspace.query().where('workspaceId', id).where('userId', user.id).delete() return response.send({ message: 'Successfully deleted workspace', - }); + }) } // List all workspaces a user has created - public async list({ request, response, auth }: HttpContextContract) { + public async list({ request, response, auth }: HttpContext) { // @ts-expect-error Property 'user' does not exist on type 'HttpContextContract'. - const user = auth.user ?? request.user; + const user = auth.user ?? request.user if (!user) { - return response.unauthorized('Missing or invalid api token'); + return response.unauthorized('Missing or invalid api token') } - const workspaces = await user.related('workspaces').query(); + const workspaces = await user.related('workspaces').query() // Convert to array with all data Franz wants - let workspacesArray: object[] = []; + let workspacesArray: object[] = [] if (workspaces) { // eslint-disable-next-line @typescript-eslint/no-explicit-any workspacesArray = workspaces.map((workspace: any) => ({ @@ -177,9 +169,9 @@ export default class WorkspaceController { ? JSON.parse(workspace.services) : workspace.services, userId: user.id, - })); + })) } - return response.send(workspacesArray); + return response.send(workspacesArray) } } 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 @@ | */ -import Logger from '@ioc:Adonis/Core/Logger'; -import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'; +import logger from '@adonisjs/core/services/logger' +import { ExceptionHandler as AdonisExceptionHandler } from '@adonisjs/core/http' -export default class ExceptionHandler extends HttpExceptionHandler { +export default class ExceptionHandler extends AdonisExceptionHandler { constructor() { - super(Logger); + super(logger) } } 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 @@ -import { GuardsList } from '@ioc:Adonis/Addons/Auth'; -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import { AuthenticationException } from '@adonisjs/auth/build/standalone'; +import { GuardsList } from '@ioc:Adonis/Addons/Auth' +import { HttpContext } from '@adonisjs/core/http' +import { AuthenticationException } from '@adonisjs/auth/build/standalone' /** * This is actually a reverted a reverted auth middleware available in ./Auth.ts @@ -10,27 +10,24 @@ export default class GuestMiddleware { /** * The URL to redirect to when request is authorized */ - protected redirectTo = '/dashboard'; + protected redirectTo = '/dashboard' - protected async authenticate( - auth: HttpContextContract['auth'], - guards: (keyof GuardsList)[], - ) { - let guardLastAttempted: string | undefined; + protected async authenticate(auth: HttpContext['auth'], guards: (keyof GuardsList)[]) { + let guardLastAttempted: string | undefined for (const guard of guards) { - guardLastAttempted = guard; + guardLastAttempted = guard // eslint-disable-next-line no-await-in-loop if (await auth.use(guard).check()) { - auth.defaultGuard = guard; + auth.defaultGuard = guard throw new AuthenticationException( 'Unauthorized access', 'E_UNAUTHORIZED_ACCESS', guardLastAttempted, - this.redirectTo, - ); + this.redirectTo + ) } } } @@ -39,18 +36,18 @@ export default class GuestMiddleware { * Handle request */ public async handle( - { auth }: HttpContextContract, + { auth }: HttpContext, next: () => Promise, - customGuards: (keyof GuardsList)[], + customGuards: (keyof GuardsList)[] ) { /** * Uses the user defined guards or the default guard mentioned in * the config file */ - const guards = customGuards.length > 0 ? customGuards : [auth.name]; + const guards = customGuards.length > 0 ? customGuards : [auth.name] - await this.authenticate(auth, guards); + await this.authenticate(auth, guards) - await next(); + await next() } } 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 @@ -import { GuardsList } from '@ioc:Adonis/Addons/Auth'; -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import { AuthenticationException } from '@adonisjs/auth/build/standalone'; -import * as jose from 'jose'; -import { appKey } from 'Config/app'; -import User from 'App/Models/User'; +import { GuardsList } from '@ioc:Adonis/Addons/Auth' +import { HttpContext } from '@adonisjs/core/http' +import { AuthenticationException } from '@adonisjs/auth/build/standalone' +import * as jose from 'jose' +import { appKey } from '#config/app' +import User from '#app/Models/User' /** * Auth middleware is meant to restrict un-authenticated access to a given route @@ -16,7 +16,7 @@ export default class AuthMiddleware { /** * The URL to redirect to when request is Unauthorized */ - protected redirectTo = '/user/login'; + protected redirectTo = '/user/login' /** * Authenticates the current HTTP request against a custom set of defined @@ -27,9 +27,9 @@ export default class AuthMiddleware { * during the current request. */ protected async authenticate( - auth: HttpContextContract['auth'], + auth: HttpContext['auth'], guards: (keyof GuardsList)[], - request: HttpContextContract['request'], + request: HttpContext['request'] ) { /** * Hold reference to the guard last attempted within the for loop. We pass @@ -37,15 +37,15 @@ export default class AuthMiddleware { * it can decide the correct response behavior based upon the guard * driver */ - let guardLastAttempted: string | undefined; + let guardLastAttempted: string | undefined for (const guard of guards) { - guardLastAttempted = guard; + guardLastAttempted = guard - let isLoggedIn = false; + let isLoggedIn = false try { // eslint-disable-next-line no-await-in-loop - isLoggedIn = await auth.use(guard).check(); + isLoggedIn = await auth.use(guard).check() } catch { // Silent fail to allow the rest of the code to handle the error } @@ -56,25 +56,22 @@ export default class AuthMiddleware { * the rest of the request, since the user authenticated * succeeded here */ - auth.defaultGuard = guard; - return; + auth.defaultGuard = guard + return } } // Manually try authenticating using the JWT (verfiy signature required) // Legacy support for JWTs so that the client still works (older than 2.0.0) - const authToken = request.headers().authorization?.split(' ')[1]; + const authToken = request.headers().authorization?.split(' ')[1] if (authToken) { try { - const jwt = await jose.jwtVerify( - authToken, - new TextEncoder().encode(appKey), - ); - const { uid } = jwt.payload; + const jwt = await jose.jwtVerify(authToken, new TextEncoder().encode(appKey)) + const { uid } = jwt.payload // @ts-expect-error - request.user = await User.findOrFail(uid); - return; + request.user = await User.findOrFail(uid) + return } catch { // Silent fail to allow the rest of the code to handle the error } @@ -87,32 +84,32 @@ export default class AuthMiddleware { 'Unauthorized access', 'E_UNAUTHORIZED_ACCESS', guardLastAttempted, - this.redirectTo, - ); + this.redirectTo + ) } /** * Handle request */ public async handle( - { request, auth, response }: HttpContextContract, + { request, auth, response }: HttpContext, next: () => Promise, - customGuards: (keyof GuardsList)[], + customGuards: (keyof GuardsList)[] ) { /** * Uses the user defined guards or the default guard mentioned in * the config file */ - const guards = customGuards.length > 0 ? customGuards : [auth.name]; + const guards = customGuards.length > 0 ? customGuards : [auth.name] try { - await this.authenticate(auth, guards, request); + await this.authenticate(auth, guards, request) } catch (error) { // If the user is not authenticated and it is a web endpoint, redirect to the login page if (guards.includes('web')) { - return response.redirect(error.redirectTo); + return response.redirect(error.redirectTo) } - throw error; + throw error } - await next(); + await next() } } 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 @@ -import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; -import Config from '@ioc:Adonis/Core/Config'; +import type { HttpContext } from '@adonisjs/core/http' +import { Config } from '@adonisjs/core/config' export default class Dashboard { - public async handle( - { response }: HttpContextContract, - next: () => Promise, - ) { + public async handle({ response }: HttpContext, next: () => Promise) { if (Config.get('dashboard.enabled') === false) { response.send( - '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.', - ); + '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.' + ) } else { - await next(); + await next() } } } 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 @@ -import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import { HttpContext } from '@adonisjs/core/http' /** * Silent auth middleware can be used as a global middleware to silent check @@ -10,15 +10,12 @@ export default class SilentAuthMiddleware { /** * Handle request */ - public async handle( - { auth }: HttpContextContract, - next: () => Promise, - ) { + public async handle({ auth }: HttpContext, next: () => Promise) { /** * Check if user is logged-in or not. If yes, then `ctx.auth.user` will be * set to the instance of the currently logged in user. */ - await auth.check(); - await next(); + await auth.check() + await next() } } 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 @@ -import { DateTime } from 'luxon'; -import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'; +import { DateTime } from 'luxon' +import { BaseModel, column } from '@adonisjs/lucid/orm' export default class Recipe extends BaseModel { @column({ isPrimary: true }) - public id: number; + public id: number @column() - public name: string; + public name: string @column() - public recipeId: string; + public recipeId: string // TODO: Type the data object. @column() - public data: object; + public data: object @column.dateTime({ autoCreate: true }) - public createdAt: DateTime; + public createdAt: DateTime @column.dateTime({ autoCreate: true, autoUpdate: true }) - public updatedAt: DateTime; + public updatedAt: DateTime } 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 @@ -import { DateTime } from 'luxon'; -import { BaseModel, column, HasOne, hasOne } from '@ioc:Adonis/Lucid/Orm'; -import User from './User'; +import { DateTime } from 'luxon' +import { BaseModel, column, hasOne } from '@adonisjs/lucid/orm' +import User from './User.js' +import type { HasOne } from '@adonisjs/lucid/types/relations' export default class Service extends BaseModel { @column({ isPrimary: true }) - public id: number; + public id: number @hasOne(() => User, { foreignKey: 'userId', }) - public user: HasOne; + public user: HasOne @column({ columnName: 'userId', }) - public userId: number; + public userId: number @column({ columnName: 'serviceId', }) - public serviceId: string; + public serviceId: string @column() - public name: string; + public name: string @column({ columnName: 'recipeId', }) - public recipeId: string; + public recipeId: string @column() - public settings: string; + public settings: string @column.dateTime({ autoCreate: true }) - public createdAt: DateTime; + public createdAt: DateTime @column.dateTime({ autoCreate: true, autoUpdate: true }) - public updatedAt: DateTime; + public updatedAt: DateTime } 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 @@ -import { DateTime } from 'luxon'; -import { BaseModel, column, HasOne, hasOne } from '@ioc:Adonis/Lucid/Orm'; -import User from './User'; +import { DateTime } from 'luxon' +import { BaseModel, column, hasOne } from '@adonisjs/lucid/orm' +import User from './User.js' +import { HasOne } from '@adonisjs/lucid/types/relations' export default class Token extends BaseModel { @column({ isPrimary: true }) - public id: number; + public id: number @hasOne(() => User, { localKey: 'user_id', foreignKey: 'id', }) - public user: HasOne; + public user: HasOne @column() - public user_id: number; + public user_id: number @column() - public token: string; + public token: string @column() - public type: string; + public type: string @column() - public is_revoked: boolean; + public is_revoked: boolean @column() - public name: string; + public name: string @column.dateTime() - public expires_at: DateTime; + public expires_at: DateTime @column.dateTime({ autoCreate: true }) - public created_at: DateTime; + public created_at: DateTime @column.dateTime({ autoCreate: true, autoUpdate: true }) - public updated_at: DateTime; + public updated_at: DateTime } 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 @@ -import { DateTime } from 'luxon'; -import { - BaseModel, - beforeSave, - column, - HasMany, - hasMany, -} from '@ioc:Adonis/Lucid/Orm'; -import Hash from '@ioc:Adonis/Core/Hash'; -import Event from '@ioc:Adonis/Core/Event'; -import moment from 'moment'; -import Encryption from '@ioc:Adonis/Core/Encryption'; -import randtoken from 'rand-token'; -import Token from './Token'; -import Workspace from './Workspace'; -import Service from './Service'; -import Mail from '@ioc:Adonis/Addons/Mail'; -import { url } from 'Config/app'; -import { mailFrom } from 'Config/dashboard'; +import { DateTime } from 'luxon' +import { BaseModel, beforeSave, column, hasMany } from '@adonisjs/lucid/orm' +import hash from '@adonisjs/core/services/hash' +import emitter from '@adonisjs/core/services/emitter' +import moment from 'moment' +import Encryption from '@ioc:Adonis/Core/Encryption' +import randtoken from 'rand-token' +import Token from './Token.js' +import Workspace from './Workspace.js' +import Service from './Service.js' +import mail from '@adonisjs/mail/services/main' +import { url } from '#config/app' +import { mailFrom } from '#config/dashboard' +import { HasMany } from '@adonisjs/lucid/types/relations' export default class User extends BaseModel { @column({ isPrimary: true }) - public id: number; + public id: number @column() - public email: string; + public email: string @column() - public username: string; + public username: string @column() - public password: string; + public password: string @column() - public lastname: string; + public lastname: string // TODO: Type the settings object. @column() - public settings: object; + public settings: object @column.dateTime({ autoCreate: true }) - public created_at: DateTime; + public created_at: DateTime @column.dateTime({ autoCreate: true, autoUpdate: true }) - public updated_at: DateTime; + public updated_at: DateTime @beforeSave() public static async hashPassword(user: User) { if (user.$dirty.password) { - user.password = await Hash.make(user.password); + user.password = await hash.make(user.password) } } @hasMany(() => Token, { foreignKey: 'user_id', }) - public tokens: HasMany; + public tokens: HasMany @hasMany(() => Service, { foreignKey: 'userId', }) - public services: HasMany; + public services: HasMany @hasMany(() => Workspace, { foreignKey: 'userId', }) - public workspaces: HasMany; + public workspaces: HasMany public async forgotPassword(): Promise { - const token = await this.generateToken(this, 'forgot_password'); + const token = await this.generateToken(this, 'forgot_password') - await Mail.send(message => { + await mail.send((message) => { message .from(mailFrom) .to(this.email) @@ -78,13 +73,13 @@ export default class User extends BaseModel { username: this.username, appUrl: url, token: token, - }); - }); + }) + }) - await Event.emit('forgot:password', { + await emitter.emit('forgot:password', { user: this, token, - }); + }) } private async generateToken(user: User, type: string): Promise { @@ -93,21 +88,17 @@ export default class User extends BaseModel { .query() .where('type', type) .where('is_revoked', false) - .where( - 'updated_at', - '>=', - moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss'), - ); + .where('updated_at', '>=', moment().subtract(24, 'hours').format('YYYY-MM-DD HH:mm:ss')) - const row = await query.first(); + const row = await query.first() if (row) { - return row.token; + return row.token } - const token = Encryption.encrypt(randtoken.generate(16)); + const token = Encryption.encrypt(randtoken.generate(16)) - await user.related('tokens').create({ type, token }); + await user.related('tokens').create({ type, token }) - return token; + return token } } 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 @@ -import { DateTime } from 'luxon'; -import { BaseModel, column, HasOne, hasOne } from '@ioc:Adonis/Lucid/Orm'; -import User from './User'; +import { DateTime } from 'luxon' +import { BaseModel, column, hasOne } from '@adonisjs/lucid/orm' +import User from './User.js' +import { HasOne } from '@adonisjs/lucid/types/relations' export default class Workspace extends BaseModel { @column({ isPrimary: true }) - public id: number; + public id: number @column({ columnName: 'workspaceId', }) - public workspaceId: string; + public workspaceId: string @hasOne(() => User, { foreignKey: 'userId', }) - public user: HasOne; + public user: HasOne @column({ columnName: 'userId', }) - public userId: number; + public userId: number @column() - public name: string; + public name: string @column() - public order: number; + public order: number @column() - public services: string; + public services: string @column() - public data: string; + public data: string @column.dateTime({ autoCreate: true }) - public createdAt: DateTime; + public createdAt: DateTime @column.dateTime({ autoCreate: true, autoUpdate: true }) - public updatedAt: DateTime; + public updatedAt: DateTime } -- cgit v1.2.3-70-g09d2