From e503468660a13760010a94ecda5f0625c6f47f87 Mon Sep 17 00:00:00 2001 From: Ricardo Date: Fri, 13 Oct 2023 14:12:03 +0200 Subject: Server re-build with latest AdonisJS framework & Typescript (#47) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: setup first basis structure * chore: ensure styling is loaded correctly * chore: comply to new routing syntax by replace . with / in routes/resource locations * chore: add login controller * chore: correctly use views with slash instead of dot * chore: working login + tests * chore: clean up tests * chore: add password-forgot endpoint and matching test * chore: add delete page test * chore: add logout test * chore: add reset-password route and tests * chore: remove obsolete comment * chore: add account-page and tests * chore: add data page & first step of the test * chore: add transfer/import data feature and tests * chore: add export and basic test * chore: add all static api routes with tests * Regenerate 'pnpm-lock.json' and fix bad merge conflict WIP: - Tests have been commented out since they dont work - Server doesn't start * easier dev and test runs * - remove --require-pragma from reformat-files so formatting works properly - run pnpm reformat-files over codebase - remove .json files from .eslintignore - add invalid.json file to .eslintignore - configure prettier properly in eslint config - add type jsdoc to prettier config - run adonis generate:manifest command to regenerate ace-manifest.json - specify volta in package.json - introduce typecheck npm script - remove unused .mjs extension from npm scripts - install missing type definition dependencies - add pnpm.allowedDeprecatedVersions to package.json - fix invalid extends in tsconfig.json causing TS issues throughout codebase - remove @ts-ignore throughout codebase which is not relevant anymore - enable some of the tsconfig options - remove outdated eslint-disable from codebase - change deprecated faker.company.companyName() to faker.company.name() - fix TS issues inside transfer.spec.ts * - update to latest node and pnpm versions - upgrade all non-major dependencies to latest - install missing @types/luxon dependency - add cuid to pnpm.allowedDeprecatedVersions - add esModuleInterop config option to tsconfig - migrate more deprecated faker methods to new ones - add more temporary ts-ignore to code * - update eslint config - remove trailingComma: all since default in prettier v3 - add typecheck command to prepare-code npm script - upgrade various dependencies to latest major version - update tsconfig to include only useful config options - disable some lint issues and fix others * - add test command to prepare-code - disable strictPropertyInitialization flag in tsconfig which creates issues with adonis models - update precommit hook to excute pnpm prepare-code - remove ts-ignore statements from all models * fix node and pnpm dependency update * add cross env (so that we can develop on windows) * add signup endpoint (TODO: JWT auth) * Add login endpoint * Add me and updateMe endpoints * Add service endpoint * refactor: change endpoints to use jwt * add recipes endpoint * add workspaces endpoint * fix web controllors for login and post import * Update node deps * Change auth middleware (for web) and exempt api from CSRF * Add import endpoint (franz import) * Fix export/import logic * Fix service and workspace data in user/data * Fix partial lint * chore: workaround lint issues * fix: migration naming had two . * Sync back node with recipes repo * Temporarily ignore typescript * Fix adonisrc to handle public folder static assets * Fix issue with production database * add Legacy Password Provider * Fix lint errors * Fix issue on login errors frontend * add Legacy Password Provider * Fix issue with customIcons * Fix issue with auth tokens * Update 'node' to '18.18.0' * make docker work * improve docker entrypoint (test api performance) * Add migration database script * NODE_ENV on recipes * prefer @ts-expect-error over @ts-ignore * small fixes * Update 'pnpm' to '8.7.6' * fix error catch * Automatically generate JWT Public and Private keys * Use custom Adonis5-jwt * Update code to use secret (old way, no breaking changes) * Normalize appKey * Trick to make JWT tokens on client work with new version * Fix error with new JWT logic * Change migration and how we store JWT * Fix 500 response code (needs to be 401) * Improve logic and fix bugs * Fix build and entrypoint logic * Catch error if appKey changes * Add newToken logic * Fix lint (ignore any errors) * Add build for PRs * pnpm reformat-files result * Fix some tests * Fix reset password not working (test failing) * Restore csrfTokens (disabled by accident) * Fix pnpm start command with .env * Disable failing tests on the transfer endpoint (TODO) * Add tests to PR build * Fix build * Remove unnecessary assertStatus * Add typecheck * hash password on UserFactory (fix build) * Add JWT_USE_PEM true by default (increase security) * fix name of github action --------- Co-authored-by: Vijay A Co-authored-by: Balaji Vijayakumar Co-authored-by: MCMXC <16797721+mcmxcdev@users.noreply.github.com> Co-authored-by: André Oliveira --- database/factories/ServiceFactory.ts | 8 ++++++ database/factories/TokenFactory.ts | 17 +++++++++++++ database/factories/UserFactory.ts | 21 ++++++++++++++++ database/factories/WorkspaceFactory.ts | 7 ++++++ database/factory.js | 20 --------------- database/migrations/1503250034279_user.js | 22 ---------------- database/migrations/1503250034279_user.ts | 20 +++++++++++++++ database/migrations/1503250034280_token.js | 22 ---------------- database/migrations/1503250034280_token.ts | 20 +++++++++++++++ .../migrations/1566385379883_service_schema.js | 23 ----------------- .../migrations/1566385379883_service_schema.ts | 21 ++++++++++++++++ database/migrations/1566554231482_recipe_schema.js | 21 ---------------- database/migrations/1566554231482_recipe_schema.ts | 19 ++++++++++++++ .../migrations/1566554359294_workspace_schema.js | 24 ------------------ .../migrations/1566554359294_workspace_schema.ts | 22 ++++++++++++++++ .../1612629845398_users_update_schema.js | 18 -------------- .../1612629845398_users_update_schema.ts | 15 +++++++++++ .../1658076326250_correct_token_relations.ts | 22 ++++++++++++++++ database/migrations/1696110557648_jwt_tokens.ts | 29 ++++++++++++++++++++++ 19 files changed, 221 insertions(+), 150 deletions(-) create mode 100644 database/factories/ServiceFactory.ts create mode 100644 database/factories/TokenFactory.ts create mode 100644 database/factories/UserFactory.ts create mode 100644 database/factories/WorkspaceFactory.ts delete mode 100644 database/factory.js delete mode 100644 database/migrations/1503250034279_user.js create mode 100644 database/migrations/1503250034279_user.ts delete mode 100644 database/migrations/1503250034280_token.js create mode 100644 database/migrations/1503250034280_token.ts delete mode 100644 database/migrations/1566385379883_service_schema.js create mode 100644 database/migrations/1566385379883_service_schema.ts delete mode 100644 database/migrations/1566554231482_recipe_schema.js create mode 100644 database/migrations/1566554231482_recipe_schema.ts delete mode 100644 database/migrations/1566554359294_workspace_schema.js create mode 100644 database/migrations/1566554359294_workspace_schema.ts delete mode 100644 database/migrations/1612629845398_users_update_schema.js create mode 100644 database/migrations/1612629845398_users_update_schema.ts create mode 100644 database/migrations/1658076326250_correct_token_relations.ts create mode 100644 database/migrations/1696110557648_jwt_tokens.ts (limited to 'database') diff --git a/database/factories/ServiceFactory.ts b/database/factories/ServiceFactory.ts new file mode 100644 index 0000000..f675063 --- /dev/null +++ b/database/factories/ServiceFactory.ts @@ -0,0 +1,8 @@ +import Service from 'App/Models/Service'; +import Factory from '@ioc:Adonis/Lucid/Factory'; + +export default Factory.define(Service, ({ faker }) => ({ + name: faker.company.name(), + recipeId: faker.string.alphanumeric(9), + serviceId: faker.string.alphanumeric(10), +})).build(); diff --git a/database/factories/TokenFactory.ts b/database/factories/TokenFactory.ts new file mode 100644 index 0000000..5afc679 --- /dev/null +++ b/database/factories/TokenFactory.ts @@ -0,0 +1,17 @@ +import Token from 'App/Models/Token'; +import Factory from '@ioc:Adonis/Lucid/Factory'; +import { DateTime } from 'luxon'; + +export default Factory.define(Token, async ({ faker }) => ({ + token: faker.string.alphanumeric(32), + type: 'forgot_password', + is_revoked: false, + created_at: DateTime.now(), + updated_at: DateTime.now(), +})) + .state( + 'old_token', + token => (token.updated_at = DateTime.now().minus({ hours: 25 })), + ) + .state('revoked', token => (token.is_revoked = true)) + .build(); diff --git a/database/factories/UserFactory.ts b/database/factories/UserFactory.ts new file mode 100644 index 0000000..ee6553e --- /dev/null +++ b/database/factories/UserFactory.ts @@ -0,0 +1,21 @@ +import User from 'App/Models/User'; +import Factory from '@ioc:Adonis/Lucid/Factory'; +import WorkspaceFactory from './WorkspaceFactory'; +import ServiceFactory from './ServiceFactory'; +import crypto from 'node:crypto'; + +const hashedPassword = crypto + .createHash('sha256') + .update('password') + .digest('base64'); + +export default Factory.define(User, async ({ faker }) => ({ + email: faker.internet.email(), + username: faker.internet.userName(), + password: hashedPassword, + // eslint-disable-next-line unicorn/prefer-string-replace-all + lastname: faker.person.lastName().replace(/'/g, ''), +})) + .relation('workspaces', () => WorkspaceFactory) + .relation('services', () => ServiceFactory) + .build(); diff --git a/database/factories/WorkspaceFactory.ts b/database/factories/WorkspaceFactory.ts new file mode 100644 index 0000000..40cda6b --- /dev/null +++ b/database/factories/WorkspaceFactory.ts @@ -0,0 +1,7 @@ +import Workspace from 'App/Models/Workspace'; +import Factory from '@ioc:Adonis/Lucid/Factory'; + +export default Factory.define(Workspace, ({ faker }) => ({ + name: faker.internet.userName(), + workspaceId: faker.string.alphanumeric(10), +})).build(); diff --git a/database/factory.js b/database/factory.js deleted file mode 100644 index 550c5e6..0000000 --- a/database/factory.js +++ /dev/null @@ -1,20 +0,0 @@ - -/* -|-------------------------------------------------------------------------- -| Factory -|-------------------------------------------------------------------------- -| -| Factories are used to define blueprints for database tables or Lucid -| models. Later you can use these blueprints to seed your database -| with dummy data. -| -*/ - -/** @type {import('@adonisjs/lucid/src/Factory')} */ -// const Factory = use('Factory') - -// Factory.blueprint('App/Models/User', (faker) => { -// return { -// username: faker.username() -// } -// }) diff --git a/database/migrations/1503250034279_user.js b/database/migrations/1503250034279_user.js deleted file mode 100644 index 77f3cee..0000000 --- a/database/migrations/1503250034279_user.js +++ /dev/null @@ -1,22 +0,0 @@ - -/** @type {import('@adonisjs/lucid/src/Schema')} */ -const Schema = use('Schema'); - -class UserSchema extends Schema { - up() { - this.create('users', (table) => { - table.increments(); - table.string('username', 80).notNullable(); - table.string('email', 254).notNullable().unique(); - table.string('password', 60).notNullable(); - table.json('settings'); - table.timestamps(); - }); - } - - down() { - this.drop('users'); - } -} - -module.exports = UserSchema; diff --git a/database/migrations/1503250034279_user.ts b/database/migrations/1503250034279_user.ts new file mode 100644 index 0000000..262a472 --- /dev/null +++ b/database/migrations/1503250034279_user.ts @@ -0,0 +1,20 @@ +import BaseSchema from '@ioc:Adonis/Lucid/Schema'; + +export default class extends BaseSchema { + protected tableName = 'users'; + + public async up(): Promise { + this.schema.createTable(this.tableName, table => { + table.increments(); + table.string('username', 80).notNullable(); + table.string('email', 254).notNullable().unique(); + table.string('password', 60).notNullable(); + table.json('settings'); + table.timestamps(); + }); + } + + public async down(): Promise { + this.schema.dropTable(this.tableName); + } +} diff --git a/database/migrations/1503250034280_token.js b/database/migrations/1503250034280_token.js deleted file mode 100644 index ad97dba..0000000 --- a/database/migrations/1503250034280_token.js +++ /dev/null @@ -1,22 +0,0 @@ - -/** @type {import('@adonisjs/lucid/src/Schema')} */ -const Schema = use('Schema'); - -class TokensSchema extends Schema { - up() { - this.create('tokens', (table) => { - table.increments(); - table.integer('user_id').unsigned().references('id').inTable('users'); - table.string('token', 255).notNullable().unique().index(); - table.string('type', 80).notNullable(); - table.boolean('is_revoked').defaultTo(false); - table.timestamps(); - }); - } - - down() { - this.drop('tokens'); - } -} - -module.exports = TokensSchema; diff --git a/database/migrations/1503250034280_token.ts b/database/migrations/1503250034280_token.ts new file mode 100644 index 0000000..5a030d0 --- /dev/null +++ b/database/migrations/1503250034280_token.ts @@ -0,0 +1,20 @@ +import BaseSchema from '@ioc:Adonis/Lucid/Schema'; + +export default class extends BaseSchema { + protected tableName = 'tokens'; + + public async up(): Promise { + this.schema.createTable(this.tableName, table => { + table.increments(); + table.integer('user_id').unsigned().references('users.id'); + table.string('token', 255).notNullable().unique().index(); + table.string('type', 80).notNullable(); + table.boolean('is_revoked').defaultTo(false); + table.timestamps(); + }); + } + + public async down(): Promise { + this.schema.dropTable(this.tableName); + } +} diff --git a/database/migrations/1566385379883_service_schema.js b/database/migrations/1566385379883_service_schema.js deleted file mode 100644 index 093fb13..0000000 --- a/database/migrations/1566385379883_service_schema.js +++ /dev/null @@ -1,23 +0,0 @@ - -/** @type {import('@adonisjs/lucid/src/Schema')} */ -const Schema = use('Schema'); - -class ServiceSchema extends Schema { - up() { - this.create('services', (table) => { - table.increments(); - table.string('userId', 80).notNullable(); - table.string('serviceId', 80).notNullable(); - table.string('name', 80).notNullable(); - table.string('recipeId', 254).notNullable(); - table.json('settings'); - table.timestamps(); - }); - } - - down() { - this.drop('services'); - } -} - -module.exports = ServiceSchema; diff --git a/database/migrations/1566385379883_service_schema.ts b/database/migrations/1566385379883_service_schema.ts new file mode 100644 index 0000000..9c3e23d --- /dev/null +++ b/database/migrations/1566385379883_service_schema.ts @@ -0,0 +1,21 @@ +import BaseSchema from '@ioc:Adonis/Lucid/Schema'; + +export default class extends BaseSchema { + protected tableName = 'services'; + + public async up(): Promise { + this.schema.createTable(this.tableName, table => { + table.increments(); + table.string('userId', 80).notNullable(); + table.string('serviceId', 80).notNullable(); + table.string('name', 80).notNullable(); + table.string('recipeId', 254).notNullable(); + table.json('settings'); + table.timestamps(); + }); + } + + public async down(): Promise { + this.schema.dropTable(this.tableName); + } +} diff --git a/database/migrations/1566554231482_recipe_schema.js b/database/migrations/1566554231482_recipe_schema.js deleted file mode 100644 index 14fcb82..0000000 --- a/database/migrations/1566554231482_recipe_schema.js +++ /dev/null @@ -1,21 +0,0 @@ - -/** @type {import('@adonisjs/lucid/src/Schema')} */ -const Schema = use('Schema'); - -class RecipeSchema extends Schema { - up() { - this.create('recipes', (table) => { - table.increments(); - table.string('name', 80).notNullable(); - table.string('recipeId', 254).notNullable().unique(); - table.json('data'); - table.timestamps(); - }); - } - - down() { - this.drop('recipes'); - } -} - -module.exports = RecipeSchema; diff --git a/database/migrations/1566554231482_recipe_schema.ts b/database/migrations/1566554231482_recipe_schema.ts new file mode 100644 index 0000000..3a9784d --- /dev/null +++ b/database/migrations/1566554231482_recipe_schema.ts @@ -0,0 +1,19 @@ +import BaseSchema from '@ioc:Adonis/Lucid/Schema'; + +export default class extends BaseSchema { + protected tableName = 'recipes'; + + public async up(): Promise { + this.schema.createTable(this.tableName, table => { + table.increments(); + table.string('name', 80).notNullable(); + table.string('recipeId', 254).notNullable().unique(); + table.json('data'); + table.timestamps(); + }); + } + + public async down(): Promise { + this.schema.dropTable(this.tableName); + } +} diff --git a/database/migrations/1566554359294_workspace_schema.js b/database/migrations/1566554359294_workspace_schema.js deleted file mode 100644 index 0a3c138..0000000 --- a/database/migrations/1566554359294_workspace_schema.js +++ /dev/null @@ -1,24 +0,0 @@ - -/** @type {import('@adonisjs/lucid/src/Schema')} */ -const Schema = use('Schema'); - -class WorkspaceSchema extends Schema { - up() { - this.create('workspaces', (table) => { - table.increments(); - table.string('workspaceId', 80).notNullable().unique(); - table.string('userId', 80).notNullable(); - table.string('name', 80).notNullable(); - table.integer('order'); - table.json('services'); - table.json('data'); - table.timestamps(); - }); - } - - down() { - this.drop('workspaces'); - } -} - -module.exports = WorkspaceSchema; diff --git a/database/migrations/1566554359294_workspace_schema.ts b/database/migrations/1566554359294_workspace_schema.ts new file mode 100644 index 0000000..77e1189 --- /dev/null +++ b/database/migrations/1566554359294_workspace_schema.ts @@ -0,0 +1,22 @@ +import BaseSchema from '@ioc:Adonis/Lucid/Schema'; + +export default class extends BaseSchema { + protected tableName = 'workspaces'; + + public async up(): Promise { + this.schema.createTable(this.tableName, table => { + table.increments(); + table.string('workspaceId', 80).notNullable().unique(); + table.string('userId', 80).notNullable(); + table.string('name', 80).notNullable(); + table.integer('order'); + table.json('services'); + table.json('data'); + table.timestamps(); + }); + } + + public async down(): Promise { + this.schema.dropTable(this.tableName); + } +} diff --git a/database/migrations/1612629845398_users_update_schema.js b/database/migrations/1612629845398_users_update_schema.js deleted file mode 100644 index 10b82aa..0000000 --- a/database/migrations/1612629845398_users_update_schema.js +++ /dev/null @@ -1,18 +0,0 @@ -/** @type {import('@adonisjs/lucid/src/Schema')} */ -const Schema = use('Schema'); - -class UsersUpdateSchema extends Schema { - up() { - this.table('users', (table) => { - table.string('lastname', 80).notNullable().default(''); - }); - } - - down() { - this.table('users', (table) => { - table.dropColumn('lastname'); - }); - } -} - -module.exports = UsersUpdateSchema; diff --git a/database/migrations/1612629845398_users_update_schema.ts b/database/migrations/1612629845398_users_update_schema.ts new file mode 100644 index 0000000..8831ea4 --- /dev/null +++ b/database/migrations/1612629845398_users_update_schema.ts @@ -0,0 +1,15 @@ +import BaseSchema from '@ioc:Adonis/Lucid/Schema'; + +export default class extends BaseSchema { + public async up(): Promise { + this.schema.alterTable('users', table => { + table.string('lastname', 80).notNullable().defaultTo(''); + }); + } + + public async down(): Promise { + this.schema.alterTable('users', table => { + table.dropColumn('lastname'); + }); + } +} diff --git a/database/migrations/1658076326250_correct_token_relations.ts b/database/migrations/1658076326250_correct_token_relations.ts new file mode 100644 index 0000000..5486657 --- /dev/null +++ b/database/migrations/1658076326250_correct_token_relations.ts @@ -0,0 +1,22 @@ +import BaseSchema from '@ioc:Adonis/Lucid/Schema'; + +export default class extends BaseSchema { + protected tableName = 'tokens'; + + public async up(): Promise { + await this.db.rawQuery( + 'DELETE FROM tokens WHERE user_id NOT IN (SELECT id FROM users)', + ); + + this.schema.alterTable(this.tableName, table => { + table.dropForeign('user_id'); + + table.foreign('user_id').references('users.id').onDelete('cascade'); + }); + } + + public async down(): Promise { + // Don't set it back withouth onDelete as the + // tests will break. + } +} diff --git a/database/migrations/1696110557648_jwt_tokens.ts b/database/migrations/1696110557648_jwt_tokens.ts new file mode 100644 index 0000000..9400de7 --- /dev/null +++ b/database/migrations/1696110557648_jwt_tokens.ts @@ -0,0 +1,29 @@ +import BaseSchema from '@ioc:Adonis/Lucid/Schema'; + +export default class JwtTokens extends BaseSchema { + protected tableName = 'jwt_tokens'; + + public async up() { + this.schema.createTable(this.tableName, table => { + table.increments('id').primary(); + table + .integer('user_id') + .unsigned() + .references('users.id') + .onDelete('CASCADE'); + table.string('name').notNullable(); + table.string('type').notNullable(); + table.string('token', 64).notNullable().unique(); + table.timestamp('expires_at', { useTz: true }).nullable(); + table.string('refresh_token').notNullable().unique().index(); + table + .timestamp('refresh_token_expires_at', { useTz: true }) + .notNullable(); + table.timestamp('created_at', { useTz: true }).notNullable(); + }); + } + + public async down() { + this.schema.dropTable(this.tableName); + } +} -- cgit v1.2.3-54-g00ecf