aboutsummaryrefslogtreecommitdiffstats
path: root/start
diff options
context:
space:
mode:
authorLibravatar Ricardo <ricardo@cino.io>2023-10-13 14:12:03 +0200
committerLibravatar GitHub <noreply@github.com>2023-10-13 13:12:03 +0100
commite503468660a13760010a94ecda5f0625c6f47f87 (patch)
treefa532f54fc5f091de08d55405ec6339bd2440a02 /start
parent1.3.16 [skip ci] (diff)
downloadferdium-server-e503468660a13760010a94ecda5f0625c6f47f87.tar.gz
ferdium-server-e503468660a13760010a94ecda5f0625c6f47f87.tar.zst
ferdium-server-e503468660a13760010a94ecda5f0625c6f47f87.zip
Server re-build with latest AdonisJS framework & Typescript (#47)
* 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 <vraravam@users.noreply.github.com> Co-authored-by: Balaji Vijayakumar <kuttibalaji.v6@gmail.com> Co-authored-by: MCMXC <16797721+mcmxcdev@users.noreply.github.com> Co-authored-by: André Oliveira <oliveira.andrerodrigues95@gmail.com>
Diffstat (limited to 'start')
-rw-r--r--start/app.js65
-rw-r--r--start/events.js25
-rw-r--r--start/events.ts33
-rw-r--r--start/kernel.js62
-rw-r--r--start/kernel.ts49
-rw-r--r--start/routes.js128
-rw-r--r--start/routes.ts22
-rw-r--r--start/routes/api.ts51
-rw-r--r--start/routes/web.ts54
9 files changed, 209 insertions, 280 deletions
diff --git a/start/app.js b/start/app.js
deleted file mode 100644
index f1fb23a..0000000
--- a/start/app.js
+++ /dev/null
@@ -1,65 +0,0 @@
1/*
2|--------------------------------------------------------------------------
3| Providers
4|--------------------------------------------------------------------------
5|
6| Providers are building blocks for your Adonis app. Anytime you install
7| a new Adonis specific package, chances are you will register the
8| provider here.
9|
10*/
11const providers = [
12 '@adonisjs/framework/providers/AppProvider',
13 '@adonisjs/auth/providers/AuthProvider',
14 '@adonisjs/bodyparser/providers/BodyParserProvider',
15 '@adonisjs/cors/providers/CorsProvider',
16 '@adonisjs/lucid/providers/LucidProvider',
17 '@adonisjs/drive/providers/DriveProvider',
18 '@adonisjs/validator/providers/ValidatorProvider',
19 '@adonisjs/framework/providers/ViewProvider',
20 '@adonisjs/session/providers/SessionProvider',
21 '@adonisjs/shield/providers/ShieldProvider',
22 '@adonisjs/persona/providers/PersonaProvider',
23 '@adonisjs/mail/providers/MailProvider',
24];
25
26/*
27|--------------------------------------------------------------------------
28| Ace Providers
29|--------------------------------------------------------------------------
30|
31| Ace providers are required only when running ace commands. For example
32| Providers for migrations, tests etc.
33|
34*/
35const aceProviders = [
36 '@adonisjs/lucid/providers/MigrationsProvider',
37];
38
39/*
40|--------------------------------------------------------------------------
41| Aliases
42|--------------------------------------------------------------------------
43|
44| Aliases are short unique names for IoC container bindings. You are free
45| to create your own aliases.
46|
47| For example:
48| { Route: 'Adonis/Src/Route' }
49|
50*/
51const aliases = {};
52
53/*
54|--------------------------------------------------------------------------
55| Commands
56|--------------------------------------------------------------------------
57|
58| Here you store ace commands for your package
59|
60*/
61const commands = [];
62
63module.exports = {
64 providers, aceProviders, aliases, commands,
65};
diff --git a/start/events.js b/start/events.js
deleted file mode 100644
index a99afd5..0000000
--- a/start/events.js
+++ /dev/null
@@ -1,25 +0,0 @@
1const Event = use('Event');
2const Mail = use('Mail');
3const Env = use('Env');
4
5Event.on('forgot::password', async ({ user, token }) => {
6 const body = `
7Hello ${user.username},
8we received a request to reset your Ferdium account password.
9Use the link below to reset your password. If you didn't requested that your password be reset, please ignore this message.
10
11${Env.get('APP_URL')}/user/reset?token=${encodeURIComponent(token)}
12
13This message was sent automatically. Please do not reply.
14`;
15 console.log('Sending message', body);
16 try {
17 await Mail.raw(body, (message) => {
18 message.subject('[Ferdium] Reset your password');
19 message.from(Env.get('MAIL_SENDER'));
20 message.to(user.email);
21 });
22 } catch (e) {
23 console.log(`Couldn't send mail: ${e}`);
24 }
25});
diff --git a/start/events.ts b/start/events.ts
new file mode 100644
index 0000000..11e63e5
--- /dev/null
+++ b/start/events.ts
@@ -0,0 +1,33 @@
1import Config from '@ioc:Adonis/Core/Config';
2import Event from '@ioc:Adonis/Core/Event';
3import Mail from '@ioc:Adonis/Addons/Mail';
4
5/*
6|--------------------------------------------------------------------------
7| Preloaded File
8|--------------------------------------------------------------------------
9|
10| Any code written inside this file will be executed during the application
11| boot.
12|
13*/
14Event.on('forgot::password', async ({ user, token }) => {
15 try {
16 // eslint-disable-next-line no-console
17 console.log('Sending message');
18 await Mail.send(message => {
19 message
20 .subject('[Ferdium] Forgot Password')
21 .to(user.email)
22 .from(Config.get('dasshboard.mailFrom'))
23 .textView('emails.forgot-password', {
24 appUrl: Config.get('app.url'),
25 username: user.username,
26 token,
27 });
28 });
29 } catch (error) {
30 // eslint-disable-next-line no-console
31 console.log(`Couldn't send mail: ${error}`);
32 }
33});
diff --git a/start/kernel.js b/start/kernel.js
deleted file mode 100644
index 12c2162..0000000
--- a/start/kernel.js
+++ /dev/null
@@ -1,62 +0,0 @@
1/** @type {import('@adonisjs/framework/src/Server')} */
2const Server = use('Server');
3
4/*
5|--------------------------------------------------------------------------
6| Global Middleware
7|--------------------------------------------------------------------------
8|
9| Global middleware are executed on each http request only when the routes
10| match.
11|
12*/
13const globalMiddleware = [
14 'Adonis/Middleware/BodyParser',
15 'App/Middleware/ConvertEmptyStringsToNull',
16 'Adonis/Middleware/AuthInit',
17 'Adonis/Middleware/Session',
18];
19
20/*
21|--------------------------------------------------------------------------
22| Named Middleware
23|--------------------------------------------------------------------------
24|
25| Named middleware is key/value object to conditionally add middleware on
26| specific routes or group of routes.
27|
28| // define
29| {
30| auth: 'Adonis/Middleware/Auth'
31| }
32|
33| // use
34| Route.get().middleware('auth')
35|
36*/
37const namedMiddleware = {
38 auth: 'Adonis/Middleware/Auth',
39 guest: 'Adonis/Middleware/AllowGuestOnly',
40 shield: 'Adonis/Middleware/Shield',
41};
42
43/*
44|--------------------------------------------------------------------------
45| Server Middleware
46|--------------------------------------------------------------------------
47|
48| Server level middleware are executed even when route for a given URL is
49| not registered. Features like `static assets` and `cors` needs better
50| control over request lifecycle.
51|
52*/
53const serverMiddleware = [
54 'Adonis/Middleware/Static',
55 'Adonis/Middleware/Cors',
56 'App/Middleware/HandleDoubleSlash',
57];
58
59Server
60 .registerGlobal(globalMiddleware)
61 .registerNamed(namedMiddleware)
62 .use(serverMiddleware);
diff --git a/start/kernel.ts b/start/kernel.ts
new file mode 100644
index 0000000..1c5c92b
--- /dev/null
+++ b/start/kernel.ts
@@ -0,0 +1,49 @@
1/*
2|--------------------------------------------------------------------------
3| Application middleware
4|--------------------------------------------------------------------------
5|
6| This file is used to define middleware for HTTP requests. You can register
7| middleware as a `closure` or an IoC container binding. The bindings are
8| preferred, since they keep this file clean.
9|
10*/
11
12import Server from '@ioc:Adonis/Core/Server';
13
14/*
15|--------------------------------------------------------------------------
16| Global middleware
17|--------------------------------------------------------------------------
18|
19| An array of global middleware, that will be executed in the order they
20| are defined for every HTTP requests.
21|
22*/
23Server.middleware.register([
24 () => import('@ioc:Adonis/Core/BodyParser'),
25 () => import('@ioc:Adonis/Addons/Shield'),
26]);
27
28/*
29|--------------------------------------------------------------------------
30| Named middleware
31|--------------------------------------------------------------------------
32|
33| Named middleware are defined as key-value pair. The value is the namespace
34| or middleware function and key is the alias. Later you can use these
35| alias on individual routes. For example:
36|
37| { auth: () => import('App/Middleware/Auth') }
38|
39| and then use it as follows
40|
41| Route.get('dashboard', 'UserController.dashboard').middleware('auth')
42|
43*/
44Server.middleware.registerNamed({
45 auth: () => import('App/Middleware/Auth'),
46 dashboard: () => import('App/Middleware/Dashboard'),
47 guest: () => import('App/Middleware/AllowGuestOnly'),
48 shield: () => import('@ioc:Adonis/Addons/Shield'),
49});
diff --git a/start/routes.js b/start/routes.js
deleted file mode 100644
index 0f3785f..0000000
--- a/start/routes.js
+++ /dev/null
@@ -1,128 +0,0 @@
1/*
2|--------------------------------------------------------------------------
3| Routes
4|--------------------------------------------------------------------------
5|
6*/
7
8/** @type {typeof import('@adonisjs/framework/src/Route/Manager')} */
9const Route = use('Route');
10const Env = use('Env');
11
12// Health: Returning if all systems function correctly
13Route.get('health', ({
14 response,
15}) => response.send({
16 api: 'success',
17 db: 'success',
18}));
19
20// API is grouped under '/v1/' route
21Route.group(() => {
22 // User authentification
23 Route.post('auth/signup', 'UserController.signup').middleware('guest');
24 Route.post('auth/login', 'UserController.login').middleware('guest');
25
26 // User info
27 Route.get('me', 'UserController.me').middleware('auth');
28 Route.put('me', 'UserController.updateMe').middleware('auth');
29
30 // Service info
31 Route.post('service', 'ServiceController.create').middleware('auth');
32 Route.put('service/reorder', 'ServiceController.reorder').middleware('auth');
33 Route.put('service/:id', 'ServiceController.edit').middleware('auth');
34 Route.delete('service/:id', 'ServiceController.delete').middleware('auth');
35 Route.get('me/services', 'ServiceController.list').middleware('auth');
36 Route.get('recipe', 'ServiceController.list').middleware('auth');
37 Route.get('icon/:id', 'ServiceController.icon');
38
39 // Recipe store
40 Route.get('recipes', 'RecipeController.list');
41 Route.get('recipes/search', 'RecipeController.search');
42 Route.get('recipes/popular', 'RecipeController.popularRecipes');
43 Route.get('recipes/download/:recipe', 'RecipeController.download');
44 Route.post('recipes/update', 'RecipeController.update');
45
46 // Workspaces
47 Route.put('workspace/:id', 'WorkspaceController.edit').middleware('auth');
48 Route.delete('workspace/:id', 'WorkspaceController.delete').middleware('auth');
49 Route.post('workspace', 'WorkspaceController.create').middleware('auth');
50 Route.get('workspace', 'WorkspaceController.list').middleware('auth');
51
52 // Static responses
53 Route.get('features/:mode?', 'StaticController.features');
54 Route.get('services', 'StaticController.emptyArray');
55 Route.get('news', 'StaticController.emptyArray');
56 Route.get('announcements/:version', 'StaticController.announcement');
57}).prefix('v1');
58
59// User dashboard
60if (Env.get('IS_DASHBOARD_ENABLED') !== 'false') {
61 Route.group(() => {
62 // Auth
63 Route.get('login', ({ view }) => view.render('dashboard.login')).middleware('guest');
64 Route.post('login', 'DashboardController.login').middleware('guest').as('login');
65
66 // Reset password
67 Route.get('forgot', ({ view }) => view.render('dashboard.forgotPassword')).middleware('guest');
68 Route.post('forgot', 'DashboardController.forgotPassword').middleware('guest');
69
70 Route.get('reset', ({ view, request }) => {
71 const { token } = request.get();
72 if (token) {
73 return view.render('dashboard.resetPassword', { token });
74 }
75 return view.render('others.message', {
76 heading: 'Invalid token',
77 text: 'Please make sure you are using a valid and recent link to reset your password.',
78 });
79 }).middleware('guest');
80 Route.post('reset', 'DashboardController.resetPassword').middleware('guest');
81
82 // Dashboard
83 Route.get('account', 'DashboardController.account').middleware('auth:session');
84 Route.post('account', 'DashboardController.edit').middleware('auth:session');
85
86 Route.get('data', 'DashboardController.data').middleware('auth:session');
87
88 Route.get('export', 'DashboardController.export').middleware('auth:session');
89 Route.post('transfer', 'DashboardController.import').middleware('auth:session');
90 Route.get('transfer', ({ view }) => view.render('dashboard.transfer')).middleware('auth:session');
91
92 Route.get('delete', ({ view }) => view.render('dashboard.delete')).middleware('auth:session');
93 Route.post('delete', 'DashboardController.delete').middleware('auth:session');
94
95 Route.get('logout', 'DashboardController.logout').middleware('auth:session');
96
97 Route.get('*', ({ response }) => response.redirect('/user/account'));
98 }).prefix('user').middleware('shield');
99} else {
100 Route.group(() => {
101 Route.get('*', ({
102 response,
103 }) => 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.'));
104 }).prefix('user');
105}
106
107// Recipe creation
108Route.post('new', 'RecipeController.create');
109Route.get('new', ({ response, view }) => {
110 if (Env.get('IS_CREATION_ENABLED') == 'false') { // eslint-disable-line eqeqeq
111 return response.send('This server doesn\'t allow the creation of new recipes.\n\nIf you are the server owner, please set IS_CREATION_ENABLED to true to enable recipe creation.');
112 }
113 return view.render('others.new');
114});
115
116// Franz/Ferdi account import
117Route.post('import', 'UserController.import');
118Route.get('import', ({ view }) => view.render('others.import'));
119
120// Legal documents
121Route.get('terms', ({ response }) => response.redirect('/terms.html'));
122Route.get('privacy', ({ response }) => response.redirect('/privacy.html'));
123
124// Index
125Route.get('/', ({ view }) => view.render('others.index'));
126
127// 404 handler
128Route.get('/*', ({ response }) => response.redirect('/'));
diff --git a/start/routes.ts b/start/routes.ts
new file mode 100644
index 0000000..75cef72
--- /dev/null
+++ b/start/routes.ts
@@ -0,0 +1,22 @@
1/*
2|--------------------------------------------------------------------------
3| Routes
4|--------------------------------------------------------------------------
5|
6| This file is dedicated for defining HTTP routes. A single file is enough
7| for majority of projects, however you can define routes in different
8| files and just make sure to import them inside this file. For example
9|
10| Define routes in following two files
11| ├── start/routes/cart.ts
12| ├── start/routes/customer.ts
13|
14| and then import them inside `start/routes.ts` as follows
15|
16| import './routes/cart'
17| import './routes/customer'
18|
19*/
20
21import './routes/api';
22import './routes/web';
diff --git a/start/routes/api.ts b/start/routes/api.ts
new file mode 100644
index 0000000..78282f0
--- /dev/null
+++ b/start/routes/api.ts
@@ -0,0 +1,51 @@
1// As this is currently a rebuild of the initial API we it is grouped in /v2/
2
3import Route from '@ioc:Adonis/Core/Route';
4
5Route.group(() => {
6 // User authentification
7 Route.post('auth/signup', 'UserController.signup').middleware('guest');
8 Route.post('auth/login', 'UserController.login').middleware('guest');
9
10 // User info
11 Route.get('me', 'UserController.me').middleware('auth:jwt');
12 Route.put('me', 'UserController.updateMe').middleware('auth:jwt');
13 Route.get('me/newtoken', 'UserController.newToken').middleware('auth:jwt');
14
15 // // Service info
16 Route.post('service', 'ServiceController.create').middleware('auth:jwt');
17 Route.put('service/reorder', 'ServiceController.reorder').middleware(
18 'auth:jwt',
19 );
20 Route.put('service/:id', 'ServiceController.edit').middleware('auth:jwt');
21 Route.delete('service/:id', 'ServiceController.delete').middleware(
22 'auth:jwt',
23 );
24 Route.get('me/services', 'ServiceController.list').middleware('auth:jwt');
25 Route.get('recipe', 'ServiceController.list').middleware('auth:jwt');
26 Route.get('icon/:id', 'ServiceController.icon');
27
28 // Recipe store
29 Route.get('recipes', 'RecipeController.list');
30 Route.get('recipes/search', 'RecipeController.search');
31 Route.get('recipes/popular', 'RecipeController.popularRecipes');
32 Route.get('recipes/download/:recipe', 'RecipeController.download');
33 Route.post('recipes/update', 'RecipeController.update');
34
35 // // Workspaces
36 Route.put('workspace/:id', 'WorkspaceController.edit').middleware('auth:jwt');
37 Route.delete('workspace/:id', 'WorkspaceController.delete').middleware(
38 'auth:jwt',
39 );
40 Route.post('workspace', 'WorkspaceController.create').middleware('auth:jwt');
41 Route.get('workspace', 'WorkspaceController.list').middleware('auth:jwt');
42
43 // Static responses
44 Route.get('features/:mode?', 'Api/Static/FeaturesController.show');
45 Route.get('services', 'Api/Static/EmptyController.show');
46 Route.get('news', 'Api/Static/EmptyController.show');
47 Route.get(
48 'announcements/:version',
49 'Api/Static/AnnouncementsController.show',
50 );
51}).prefix('/v1');
diff --git a/start/routes/web.ts b/start/routes/web.ts
new file mode 100644
index 0000000..308abec
--- /dev/null
+++ b/start/routes/web.ts
@@ -0,0 +1,54 @@
1import Route from '@ioc:Adonis/Core/Route';
2
3// Health check
4Route.get('health', 'HealthController.index');
5
6// Legal documents
7Route.get('terms', ({ response }) => response.redirect('/terms.html'));
8Route.get('privacy', ({ response }) => response.redirect('/privacy.html'));
9
10// Index
11Route.get('/', ({ view }) => view.render('others/index'));
12
13Route.group(() => {
14 Route.group(() => {
15 // Guest troutes
16 Route.group(() => {
17 Route.get('login', 'Dashboard/LoginController.show');
18 Route.post('login', 'Dashboard/LoginController.login').as('login');
19
20 // Reset password
21 Route.get('forgot', 'Dashboard/ForgotPasswordController.show');
22 Route.post('forgot', 'Dashboard/ForgotPasswordController.forgotPassword');
23
24 Route.get('reset', 'Dashboard/ResetPasswordController.show');
25 Route.post('reset', 'Dashboard/ResetPasswordController.resetPassword');
26 }).middleware(['dashboard', 'guest']);
27
28 // Authenticated routes
29 Route.group(() => {
30 Route.get('account', 'Dashboard/AccountController.show');
31 Route.post('account', 'Dashboard/AccountController.store');
32
33 Route.get('data', 'Dashboard/DataController.show');
34 Route.get('export', 'Dashboard/ExportController.show');
35
36 Route.get('transfer', 'Dashboard/TransferController.show');
37 Route.post('transfer', 'Dashboard/TransferController.import');
38
39 Route.get('delete', 'Dashboard/DeleteController.show');
40 Route.post('delete', 'Dashboard/DeleteController.delete');
41
42 Route.get('logout', 'Dashboard/LogOutController.logout');
43
44 Route.get('*', ({ response }) => response.redirect('/user/account'));
45 }).middleware(['dashboard', 'auth:web']);
46 }).prefix('user');
47
48 // Franz/Ferdi account import
49 Route.get('import', ({ view }) => view.render('others/import'));
50 Route.post('import', 'UserController.import');
51
52 // 404 handler
53 Route.get('/*', ({ response }) => response.redirect('/'));
54}).middleware(['dashboard']);