From e1c47572a6235fd8fd20af888ac3a11c7ae1369d Mon Sep 17 00:00:00 2001 From: MCMXC <16797721+mcmxcdev@users.noreply.github.com> Date: Sat, 10 Feb 2024 18:37:40 -0700 Subject: updates --- .../Http/Api/Static/AnnouncementsController.ts | 18 +- app/Controllers/Http/Api/Static/EmptyController.ts | 4 +- .../Http/Api/Static/FeaturesController.ts | 4 +- .../Http/Dashboard/AccountController.ts | 30 +-- app/Controllers/Http/Dashboard/DataController.ts | 10 +- app/Controllers/Http/Dashboard/DeleteController.ts | 10 +- app/Controllers/Http/Dashboard/ExportController.ts | 27 +-- .../Http/Dashboard/ForgotPasswordController.ts | 18 +- app/Controllers/Http/Dashboard/LogOutController.ts | 6 +- app/Controllers/Http/Dashboard/LoginController.ts | 49 ++--- .../Http/Dashboard/ResetPasswordController.ts | 49 +++-- .../Http/Dashboard/TransferController.ts | 67 +++--- app/Controllers/Http/HealthController.ts | 2 +- app/Controllers/Http/HomeController.ts | 2 +- app/Controllers/Http/RecipeController.ts | 165 ++++++++------- app/Controllers/Http/ServiceController.ts | 173 +++++++++------- app/Controllers/Http/UserController.ts | 229 +++++++++++---------- app/Controllers/Http/WorkspaceController.ts | 85 ++++---- app/Exceptions/Handler.ts | 6 +- app/Middleware/AllowGuestOnly.ts | 31 +-- app/Middleware/Auth.ts | 57 ++--- app/Middleware/Dashboard.ts | 10 +- app/Middleware/SilentAuth.ts | 6 +- app/Models/Recipe.ts | 16 +- app/Models/Service.ts | 26 +-- app/Models/Token.ts | 28 +-- app/Models/User.ts | 78 +++---- app/Models/Workspace.ts | 28 +-- 28 files changed, 662 insertions(+), 572 deletions(-) (limited to 'app') diff --git a/app/Controllers/Http/Api/Static/AnnouncementsController.ts b/app/Controllers/Http/Api/Static/AnnouncementsController.ts index 4ae9d0e..090ee1c 100644 --- a/app/Controllers/Http/Api/Static/AnnouncementsController.ts +++ b/app/Controllers/Http/Api/Static/AnnouncementsController.ts @@ -1,16 +1,20 @@ -import type { HttpContext } from '@adonisjs/core/http' -import { app } from '@adonisjs/core/services/app' -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 }: HttpContext) { - const announcement = path.join(app.resourcesPath(), 'announcements', `${params.version}.json`) + 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 ff05b1c..80d70b7 100644 --- a/app/Controllers/Http/Api/Static/EmptyController.ts +++ b/app/Controllers/Http/Api/Static/EmptyController.ts @@ -1,7 +1,7 @@ -import type { HttpContext } from '@adonisjs/core/http' +import type { HttpContext } from '@adonisjs/core/http'; export default class EmptyController { public async show({ response }: HttpContext) { - return response.send([]) + return response.send([]); } } diff --git a/app/Controllers/Http/Api/Static/FeaturesController.ts b/app/Controllers/Http/Api/Static/FeaturesController.ts index 9e14c10..ce964de 100644 --- a/app/Controllers/Http/Api/Static/FeaturesController.ts +++ b/app/Controllers/Http/Api/Static/FeaturesController.ts @@ -1,4 +1,4 @@ -import type { HttpContext } from '@adonisjs/core/http' +import type { HttpContext } from '@adonisjs/core/http'; export default class FeaturesController { public async show({ response }: HttpContext) { @@ -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 5870f19..a748c75 100644 --- a/app/Controllers/Http/Dashboard/AccountController.ts +++ b/app/Controllers/Http/Dashboard/AccountController.ts @@ -1,6 +1,6 @@ -import type { HttpContext } from '@adonisjs/core/http' -import { schema, rules, validator } from '@adonisjs/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 { /** @@ -11,7 +11,7 @@ export default class AccountController { username: auth.user?.username, email: auth.user?.email, lastname: auth.user?.lastname, - }) + }); } /** @@ -42,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', { @@ -69,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 8a77329..5f22979 100644 --- a/app/Controllers/Http/Dashboard/DataController.ts +++ b/app/Controllers/Http/Dashboard/DataController.ts @@ -1,14 +1,14 @@ -import type { HttpContext } from '@adonisjs/core/http' +import type { HttpContext } from '@adonisjs/core/http'; export default class DataController { /** * Display the data page */ public async show({ view, auth }: HttpContext) { - const { user } = auth + 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 bd824b0..76e41ca 100644 --- a/app/Controllers/Http/Dashboard/DeleteController.ts +++ b/app/Controllers/Http/Dashboard/DeleteController.ts @@ -1,20 +1,20 @@ -import type { HttpContext } from '@adonisjs/core/http' +import type { HttpContext } from '@adonisjs/core/http'; export default class DeleteController { /** * Display the delete page */ public async show({ view }: HttpContext) { - return view.render('dashboard/delete') + return view.render('dashboard/delete'); } /** * Delete user and session */ public async delete({ auth, response }: HttpContext) { - auth.user?.delete() - auth.use('web').logout() + 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 5b6df70..6b20a82 100644 --- a/app/Controllers/Http/Dashboard/ExportController.ts +++ b/app/Controllers/Http/Dashboard/ExportController.ts @@ -1,30 +1,33 @@ -import type { HttpContext } from '@adonisjs/core/http' +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 + return obj.map(item => deepParseToJSON(item)) as unknown as Record< + string, + unknown + >; } 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; } } @@ -33,9 +36,9 @@ export default class ExportController { * Display the export page */ 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 user = auth.user!; + const services = await user.related('services').query(); + const workspaces = await user.related('workspaces').query(); const exportData = { username: user.username, @@ -43,11 +46,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 f7b1d0e..1878c4d 100644 --- a/app/Controllers/Http/Dashboard/ForgotPasswordController.ts +++ b/app/Controllers/Http/Dashboard/ForgotPasswordController.ts @@ -1,13 +1,13 @@ -import type { HttpContext } from '@adonisjs/core/http' -import { schema, rules, validator } from '@adonisjs/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 }: HttpContext) { - return view.render('dashboard/forgotPassword') + return view.render('dashboard/forgotPassword'); } /** @@ -20,22 +20,22 @@ export default class ForgotPasswordController { 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 5d250c4..f085d00 100644 --- a/app/Controllers/Http/Dashboard/LogOutController.ts +++ b/app/Controllers/Http/Dashboard/LogOutController.ts @@ -1,12 +1,12 @@ -import type { HttpContext } from '@adonisjs/core/http' +import type { HttpContext } from '@adonisjs/core/http'; export default class LogOutController { /** * Login a user */ public async logout({ auth, response }: HttpContext) { - auth.logout() + 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 5a54448..3367a2f 100644 --- a/app/Controllers/Http/Dashboard/LoginController.ts +++ b/app/Controllers/Http/Dashboard/LoginController.ts @@ -1,15 +1,15 @@ -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' +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 }: HttpContext) { - return view.render('dashboard/login') + return view.render('dashboard/login'); } /** @@ -23,51 +23,54 @@ 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 b62b5d2..261d773 100644 --- a/app/Controllers/Http/Dashboard/ResetPasswordController.ts +++ b/app/Controllers/Http/Dashboard/ResetPasswordController.ts @@ -1,30 +1,35 @@ -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' +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 }: HttpContext) { - const { token } = request.qs() + 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 }: HttpContext) { + public async resetPassword({ + response, + request, + session, + view, + }: HttpContext) { try { await validator.validate({ schema: schema.create({ @@ -32,14 +37,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() @@ -47,30 +52,34 @@ 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 0296973..ab50bcf 100644 --- a/app/Controllers/Http/Dashboard/TransferController.ts +++ b/app/Controllers/Http/Dashboard/TransferController.ts @@ -1,8 +1,8 @@ -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' +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,52 +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 }: HttpContext) { - return view.render('dashboard/transfer') + return view.render('dashboard/transfer'); } public async import({ auth, request, response, session, view }: HttpContext) { - let file + 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({ @@ -67,37 +67,38 @@ 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({ @@ -107,20 +108,22 @@ 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/HealthController.ts b/app/Controllers/Http/HealthController.ts index 59094e2..bf185d8 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 669d970..bae3bc2 100644 --- a/app/Controllers/Http/HomeController.ts +++ b/app/Controllers/Http/HomeController.ts @@ -4,6 +4,6 @@ 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 e43bcf8..d30c59f 100644 --- a/app/Controllers/Http/RecipeController.ts +++ b/app/Controllers/Http/RecipeController.ts @@ -1,13 +1,13 @@ -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' +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,30 +37,34 @@ 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 }: HttpContext) { - const officialRecipes = fs.readJsonSync(path.join(app.appRoot, 'recipes', 'all.json')) - const customRecipesArray = await Recipe.all() - const customRecipes = customRecipesArray.map((recipe) => ({ + 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 @@ -68,45 +72,52 @@ export default class RecipesController { 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(app.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(app.tmpPath('recipe')) + await files.move(app.tmpPath('recipe')); // Compress files to .tar.gz file - const source = app.tmpPath('recipe') - const destination = path.join(app.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({ @@ -121,47 +132,55 @@ 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 }: 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 }: HttpContext) { @@ -169,63 +188,67 @@ export default class RecipesController { fs .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 }: HttpContext) { - const updates = [] - const recipes = request.all() - const allJson = fs.readJsonSync(path.join(app.appRoot, 'recipes', 'all.json')) + 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 }: 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 9988244..8fec844 100644 --- a/app/Controllers/Http/ServiceController.ts +++ b/app/Controllers/Http/ServiceController.ts @@ -1,49 +1,49 @@ -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' +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 }: 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,26 +72,28 @@ export default class ServiceController { ...data, }, status: ['created'], - }) + }); } // List all services a user has created 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, @@ -113,82 +115,89 @@ export default class ServiceController { name: service.name, recipeId: service.recipeId, userId: id, - } - }) + }; + }); - return response.send(servicesArray) + return response.send(servicesArray); } 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 }: 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(app.tmpPath('uploads'), iconId)) - ) - iconId = `${iconId}.${icon.extname}` + ); + iconId = `${iconId}.${icon.extname}`; 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() @@ -197,7 +206,7 @@ export default class ServiceController { .update({ name: service.name, settings: JSON.stringify(newSettings), - }) + }); return response.send({ data: { @@ -208,24 +217,28 @@ 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(app.tmpPath('uploads'), settings.iconId)).catch((error) => { - console.error(error) - }) + fs.remove(path.join(app.tmpPath('uploads'), settings.iconId)).catch( + error => { + console.error(error); + }, + ); - settings.iconId = undefined - settings.customIconVersion = undefined - settings.customIcon = '' + settings.iconId = undefined; + settings.customIconVersion = undefined; + settings.customIcon = ''; } // Update data in database @@ -235,13 +248,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: { @@ -252,19 +265,19 @@ export default class ServiceController { userId: user.id, }, status: ['updated'], - }) + }); } // TODO: Test if this works 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 @@ -272,14 +285,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 @@ -287,16 +300,18 @@ 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, @@ -318,34 +333,34 @@ export default class ServiceController { 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 }: HttpContext) { - let { id } = params + let { id } = params; - id = sanitize(id) + id = sanitize(id); if (id === '') { return response.status(404).send({ status: "Icon doesn't exist", - }) + }); } - const iconPath = path.join(app.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", - }) + }); } - return response.download(iconPath) + return response.download(iconPath); } } diff --git a/app/Controllers/Http/UserController.ts b/app/Controllers/Http/UserController.ts index 088f7b1..667786b 100644 --- a/app/Controllers/Http/UserController.ts +++ b/app/Controllers/Http/UserController.ts @@ -1,49 +1,58 @@ -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' +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.js' +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 @@ -52,44 +61,44 @@ export default class UsersController { 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 @@ -98,28 +107,30 @@ export default class UsersController { 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) { @@ -127,28 +138,31 @@ 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 }: 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', @@ -164,29 +178,29 @@ export default class UsersController { lastname: user.lastname, locale: 'en-US', ...settings, - }) + }); } 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: { @@ -205,22 +219,22 @@ export default class UsersController { ...newSettings, }, status: ['data-updated'], - }) + }); } 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 }: HttpContext) { @@ -228,114 +242,117 @@ export default class UsersController { 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({ @@ -344,34 +361,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({ @@ -381,15 +398,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 a2bc54e..6cecf69 100644 --- a/app/Controllers/Http/WorkspaceController.ts +++ b/app/Controllers/Http/WorkspaceController.ts @@ -1,53 +1,53 @@ -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' +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 }: 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 }: 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,54 +110,57 @@ export default class WorkspaceController { order: workspace.order, services: data.services, userId: user.id, - }) + }); } 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 }: 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) => ({ @@ -169,9 +172,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 b13126d..51764bc 100644 --- a/app/Exceptions/Handler.ts +++ b/app/Exceptions/Handler.ts @@ -13,11 +13,11 @@ | */ -import logger from '@adonisjs/core/services/logger' -import { ExceptionHandler as AdonisExceptionHandler } from '@adonisjs/core/http' +import logger from '@adonisjs/core/services/logger'; +import { ExceptionHandler as AdonisExceptionHandler } from '@adonisjs/core/http'; export default class ExceptionHandler extends AdonisExceptionHandler { constructor() { - super(logger) + super(logger); } } diff --git a/app/Middleware/AllowGuestOnly.ts b/app/Middleware/AllowGuestOnly.ts index 5ef5c34..75bf269 100644 --- a/app/Middleware/AllowGuestOnly.ts +++ b/app/Middleware/AllowGuestOnly.ts @@ -1,6 +1,6 @@ -import { GuardsList } from '@ioc:Adonis/Addons/Auth' -import { HttpContext } from '@adonisjs/core/http' -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,24 +10,27 @@ export default class GuestMiddleware { /** * The URL to redirect to when request is authorized */ - protected redirectTo = '/dashboard' + protected redirectTo = '/dashboard'; - protected async authenticate(auth: HttpContext['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, + ); } } } @@ -38,16 +41,16 @@ export default class GuestMiddleware { public async handle( { 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 29620bb..b6ff446 100644 --- a/app/Middleware/Auth.ts +++ b/app/Middleware/Auth.ts @@ -1,9 +1,9 @@ -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' +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 @@ -29,7 +29,7 @@ export default class AuthMiddleware { protected async authenticate( auth: HttpContext['auth'], guards: (keyof GuardsList)[], - request: HttpContext['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,22 +56,25 @@ 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 } @@ -84,8 +87,8 @@ export default class AuthMiddleware { 'Unauthorized access', 'E_UNAUTHORIZED_ACCESS', guardLastAttempted, - this.redirectTo - ) + this.redirectTo, + ); } /** @@ -94,22 +97,22 @@ export default class AuthMiddleware { public async handle( { 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 f29794c..19c8cfc 100644 --- a/app/Middleware/Dashboard.ts +++ b/app/Middleware/Dashboard.ts @@ -1,14 +1,14 @@ -import type { HttpContext } from '@adonisjs/core/http' -import { Config } from '@adonisjs/core/config' +import type { HttpContext } from '@adonisjs/core/http'; +import { Config } from '@adonisjs/core/config'; export default class Dashboard { 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 a7271d5..6ff7423 100644 --- a/app/Middleware/SilentAuth.ts +++ b/app/Middleware/SilentAuth.ts @@ -1,4 +1,4 @@ -import { HttpContext } from '@adonisjs/core/http' +import { HttpContext } from '@adonisjs/core/http'; /** * Silent auth middleware can be used as a global middleware to silent check @@ -15,7 +15,7 @@ export default class SilentAuthMiddleware { * 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 bca6e76..84497d4 100644 --- a/app/Models/Recipe.ts +++ b/app/Models/Recipe.ts @@ -1,23 +1,23 @@ -import { DateTime } from 'luxon' -import { BaseModel, column } from '@adonisjs/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 0cd2afb..0b4920b 100644 --- a/app/Models/Service.ts +++ b/app/Models/Service.ts @@ -1,41 +1,41 @@ -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' +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 a8c29dd..9c843b8 100644 --- a/app/Models/Token.ts +++ b/app/Models/Token.ts @@ -1,39 +1,39 @@ -import { DateTime } from 'luxon' -import { BaseModel, column, hasOne } from '@adonisjs/lucid/orm' -import User from './User.js' -import { HasOne } from '@adonisjs/lucid/types/relations' +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 cc2c553..d292b3a 100644 --- a/app/Models/User.ts +++ b/app/Models/User.ts @@ -1,70 +1,70 @@ -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' +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) @@ -73,13 +73,13 @@ export default class User extends BaseModel { username: this.username, appUrl: url, token: token, - }) - }) + }); + }); await emitter.emit('forgot:password', { user: this, token, - }) + }); } private async generateToken(user: User, type: string): Promise { @@ -88,17 +88,21 @@ 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 c960ae4..6c48c12 100644 --- a/app/Models/Workspace.ts +++ b/app/Models/Workspace.ts @@ -1,42 +1,42 @@ -import { DateTime } from 'luxon' -import { BaseModel, column, hasOne } from '@adonisjs/lucid/orm' -import User from './User.js' -import { HasOne } from '@adonisjs/lucid/types/relations' +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