diff --git a/.DS_Store b/.DS_Store
index 695d0da..9c0626f 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/.eslintignore b/.eslintignore
index 9385391..768fab3 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -2,6 +2,7 @@
/blueprints/*/files/
# compiled output
+/declarations/
/dist/
# misc
diff --git a/.eslintrc.js b/.eslintrc.js
index fa8f035..a2f8a27 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -48,6 +48,10 @@ module.exports = {
parser: 'ember-eslint-parser',
plugins: ['ember'],
extends: ['eslint:recommended', 'plugin:ember/recommended', 'plugin:ember/recommended-gts'],
+ rules: {
+ 'no-unused-vars': 'off',
+ 'no-undef': 'off',
+ }
},
// node files
{
@@ -57,7 +61,6 @@ module.exports = {
'./.stylelintrc.{js,cjs}',
'./.template-lintrc.{js,cjs}',
'./ember-cli-build.js',
- './playwright.config.ts',
'./testem.js',
'./tailwind.config.js',
'./blueprints/*/index.js',
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b937c91..f534a8f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,28 +12,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-node@v3
+ - uses: wyvox/action-setup-pnpm@v3
with:
node-version: 20
- - uses: pnpm/action-setup@v2
- name: Install pnpm
- with:
- version: 8
- run_install: false
- - name: Get pnpm store directory
- shell: bash
- run: |
- echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - uses: actions/cache@v3
- name: Setup pnpm cache
- with:
- path: ${{ env.STORE_PATH }}
- key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- restore-keys: |
- ${{ runner.os }}-pnpm-store-
- - name: Install dependencies
- run: pnpm install
- - name: Install Playwright Browsers
- run: npx playwright install --with-deps
- name: Run Tests
run: pnpm test
diff --git a/.husky/_/pre-push b/.husky/_/pre-push
new file mode 100755
index 0000000..cf59fe1
--- /dev/null
+++ b/.husky/_/pre-push
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+if [ "$LEFTHOOK_VERBOSE" = "1" -o "$LEFTHOOK_VERBOSE" = "true" ]; then
+ set -x
+fi
+
+if [ "$LEFTHOOK" = "0" ]; then
+ exit 0
+fi
+
+call_lefthook()
+{
+ if test -n "$LEFTHOOK_BIN"
+ then
+ "$LEFTHOOK_BIN" "$@"
+ elif lefthook -h >/dev/null 2>&1
+ then
+ lefthook "$@"
+ else
+ dir="$(git rev-parse --show-toplevel)"
+ osArch=$(uname | tr '[:upper:]' '[:lower:]')
+ cpuArch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x64/')
+ if test -f "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook"
+ then
+ "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" "$@"
+ elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook"
+ then
+ "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" "$@"
+ elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook"
+ then
+ "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" "$@"
+ elif test -f "$dir/node_modules/lefthook/bin/index.js"
+ then
+ "$dir/node_modules/lefthook/bin/index.js" "$@"
+
+ elif bundle exec lefthook -h >/dev/null 2>&1
+ then
+ bundle exec lefthook "$@"
+ elif yarn lefthook -h >/dev/null 2>&1
+ then
+ yarn lefthook "$@"
+ elif pnpm lefthook -h >/dev/null 2>&1
+ then
+ pnpm lefthook "$@"
+ elif swift package plugin lefthook >/dev/null 2>&1
+ then
+ swift package --disable-sandbox plugin lefthook "$@"
+ elif command -v mint >/dev/null 2>&1
+ then
+ mint run csjones/lefthook-plugin "$@"
+ elif command -v npx >/dev/null 2>&1
+ then
+ npx lefthook "$@"
+ else
+ echo "Can't find lefthook in PATH"
+ fi
+ fi
+}
+
+call_lefthook run "pre-push" "$@"
diff --git a/.husky/_/prepare-commit-msg b/.husky/_/prepare-commit-msg
new file mode 100755
index 0000000..e8e8dda
--- /dev/null
+++ b/.husky/_/prepare-commit-msg
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+if [ "$LEFTHOOK_VERBOSE" = "1" -o "$LEFTHOOK_VERBOSE" = "true" ]; then
+ set -x
+fi
+
+if [ "$LEFTHOOK" = "0" ]; then
+ exit 0
+fi
+
+call_lefthook()
+{
+ if test -n "$LEFTHOOK_BIN"
+ then
+ "$LEFTHOOK_BIN" "$@"
+ elif lefthook -h >/dev/null 2>&1
+ then
+ lefthook "$@"
+ else
+ dir="$(git rev-parse --show-toplevel)"
+ osArch=$(uname | tr '[:upper:]' '[:lower:]')
+ cpuArch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x64/')
+ if test -f "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook"
+ then
+ "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" "$@"
+ elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook"
+ then
+ "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" "$@"
+ elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook"
+ then
+ "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" "$@"
+ elif test -f "$dir/node_modules/lefthook/bin/index.js"
+ then
+ "$dir/node_modules/lefthook/bin/index.js" "$@"
+
+ elif bundle exec lefthook -h >/dev/null 2>&1
+ then
+ bundle exec lefthook "$@"
+ elif yarn lefthook -h >/dev/null 2>&1
+ then
+ yarn lefthook "$@"
+ elif pnpm lefthook -h >/dev/null 2>&1
+ then
+ pnpm lefthook "$@"
+ elif swift package plugin lefthook >/dev/null 2>&1
+ then
+ swift package --disable-sandbox plugin lefthook "$@"
+ elif command -v mint >/dev/null 2>&1
+ then
+ mint run csjones/lefthook-plugin "$@"
+ elif command -v npx >/dev/null 2>&1
+ then
+ npx lefthook "$@"
+ else
+ echo "Can't find lefthook in PATH"
+ fi
+ fi
+}
+
+call_lefthook run "prepare-commit-msg" "$@"
diff --git a/.husky/pre-commit b/.husky/pre-commit
deleted file mode 100755
index 7b08d54..0000000
--- a/.husky/pre-commit
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/sh
-. "$(dirname "$0")/_/husky.sh"
-
-pnpm run lint
-pnpm run test:duplication
-pnpm run test:ember --filter '!Acceptance'
-# pnpm run verify-coverage 'dry-run'
\ No newline at end of file
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..f301fed
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+auto-install-peers=false
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 7820f24..0942536 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,3 +1,3 @@
{
- "recommendations": ["typed-ember.glint-vscode","ms-playwright.playwright","dbaeumer.vscode-eslint","bradlc.vscode-tailwindcss","lifeart.vscode-ember-unstable","redhat.vscode-yaml","mikestead.dotenv","esbenp.prettier-vscode"]
+ "recommendations": ["typed-ember.glint-vscode","dbaeumer.vscode-eslint","bradlc.vscode-tailwindcss","lifeart.vscode-ember-unstable","redhat.vscode-yaml","mikestead.dotenv","esbenp.prettier-vscode"]
}
diff --git a/README.md b/README.md
index 1a01d7f..ec208a1 100644
--- a/README.md
+++ b/README.md
@@ -1,50 +1,47 @@
-# ember-boilerplate
+# Ember-boilerplate
[![Tests](https://github.com/TRIPTYK/ember-boilerplate/actions/workflows/ci.yml/badge.svg)](https://github.com/TRIPTYK/ember-boilerplate/actions/workflows/ci.yml)
-## You can use this IF
-
-- ✅ You use tailwindcss
-- ✅ You use typescript
-- ✅ You use JWT based authentification
-- ✅ You use pnpm
-- ✅ You don't bother using TRIPTYK packages
+
+
+
+
+
+
+
+
## Preconfigured 5.x Ember project with
-### Ember Side
-
- Embroider
- Typescript integration (ember-cli-typescript)
- Validations (ember-immer-changeset + yup)
- Tailwindcss 3.x, with primary and secondary colors configured
- Flash messages (ember-cli-flash)
-- Tests (ember-test-selectors, ember-cli-page-object, playwright)
+- Tests (ember-test-selectors, ember-cli-page-object)
- Sessions,Login,Logout,... (ember-simple-auth,ember-simple-auth-token)
-- Base ember adapter, serializer and controller
+- Ember data next gen (RequestManager based)
- Ember concurrency
- Test seeding & parallelization (ember-exam)
- dev & test mocking (msw)
-- e2e testing (playwright)
- Translations (ember-intl)
- Authorizations (ember-can)
- Pre-made registration flow (login/register/forgot-password)
-### VSCODE IDE
+## VSCODE IDE
- VSCODE Ready, all rules are setup for a great developing experience.
-### Additional Tooling
+## Additional Tooling
- With-backend: `with-backend.js` Starts the ember app with a backend synchronously.
-- Code duplication: with jscpd.
- Husky: checks linting + code duplication + integration & unit tests before commiting to VSC.
-### Docker
+## Docker
- A docker image can be found in `images`.
-### CI
+## CI
A github workflow CI is provided.
@@ -86,7 +83,7 @@ Components are located: `app/components/`.
### API Mocking
-Development mocks are in `public/mocks`.
+Development mocks are in `app/handlers`.
Testing mocks are split in the `tests` folders.
## Installation
@@ -99,9 +96,8 @@ Testing mocks are split in the `tests` folders.
## Running / Development
-- `ember serve`
+- `pnpm start`
- Visit your app at [http://localhost:4200](http://localhost:4200).
-- Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests).
### Running Tests
@@ -112,12 +108,7 @@ Testing mocks are split in the `tests` folders.
- `pnpm lint`
- `pnpm lint:fix`
-### Duplication
-
-- `pnpm test:duplication`
-
-
### Building
-- `ember build` (development)
-- `ember build --environment production` (production)
+- `pnpm build --mode=dev` (development)
+- `pnpm build` (production)
diff --git a/app/adapters/application.ts b/app/adapters/application.ts
deleted file mode 100644
index 09aa041..0000000
--- a/app/adapters/application.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { service } from '@ember/service';
-import JSONAPIAdapter from '@ember-data/adapter/json-api';
-
-import config from '../config/environment';
-
-import type SessionService from 'ember-boilerplate/services/session';
-import type FlashMessageService from 'ember-cli-flash/services/flash-messages';
-
-export default class ApplicationAdapter extends JSONAPIAdapter {
- @service declare session: SessionService;
- @service declare flashMessages: FlashMessageService;
-
- get host() {
- return config.host;
- }
- namespace = config.namespace;
-
- get headers(): Record {
- const headers = {
- Accept: 'application/vnd.api+json',
- 'Content-Type': 'application/vnd.api+json',
- Authorization: '',
- };
-
- if (this.session.isAuthenticated) {
- headers[
- 'Authorization'
- ] = `Bearer ${this.session.data.authenticated.accessToken}`;
- }
-
- return headers;
- }
-
- async handleResponse(
- status: number,
- headers: Record,
- payload: Record,
- requestData: Record
- ) {
- if (status === 401 && this.session.isAuthenticated) {
- this.flashMessages.danger(`Unauthorized action`);
- await this.session.invalidate();
- } else {
- if (status > 500) {
- // Internal
- this.flashMessages.danger(`Fatal error : ${payload}`);
- } else if (status > 400) {
- // Bad request
- }
- }
-
- return super.handleResponse(status, headers, payload, requestData);
- }
-
- urlForQueryRecord(
- query: Record,
- modelName: string | number
- ): string {
- const id = query['id'] as string | number;
-
- delete query['id'];
-
- return this.buildURL(modelName, id, null, 'findRecord', query);
- }
-}
-
-// DO NOT DELETE: this is how TypeScript knows how to look up your adapters.
-declare module 'ember-data/types/registries/adapter' {
- export default interface AdapterRegistry {
- application: ApplicationAdapter;
- }
-}
diff --git a/app/adapters/user.ts b/app/adapters/user.ts
deleted file mode 100644
index a4c74de..0000000
--- a/app/adapters/user.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import JSONAPIAdapter from '../adapters/application';
-
-export default class User extends JSONAPIAdapter {
- urlForQueryRecord(
- query: Record,
- modelName: string | number
- ) {
- if (query['profile']) {
- delete query['profile'];
-
- const url = `${this.host}/${this.namespace}/users/profile`;
-
- return url;
- }
-
- return super.urlForQueryRecord(query, modelName);
- }
-}
-
-// DO NOT DELETE: this is how TypeScript knows how to look up your adapters.
-declare module 'ember-data/types/registries/adapter' {
- export default interface AdapterRegistry {
- user: User;
- }
-}
diff --git a/app/app.ts b/app/app.ts
index 4b3f9d0..1a03bff 100644
--- a/app/app.ts
+++ b/app/app.ts
@@ -1,7 +1,11 @@
+import { setBuildURLConfig } from '@ember-data/request-utils';
import Application from '@ember/application';
+// @ts-expect-error
+import initializer from 'ember-simple-auth/initializers/ember-simple-auth';
+// @ts-expect-error
+import initializerJwt from '@triptyk/ember-simple-auth-token/initializers/simple-auth-token';
import config from 'ember-boilerplate/config/environment';
-import loadInitializers from 'ember-load-initializers';
import Resolver from 'ember-resolver';
export default class App extends Application {
@@ -10,4 +14,15 @@ export default class App extends Application {
Resolver = Resolver;
}
-loadInitializers(App, config.modulePrefix);
+// ember-load-initializers not working anymore, registering manually
+function loadInitializers() {
+ App.instanceInitializer(initializer);
+ App.instanceInitializer(initializerJwt);
+}
+
+setBuildURLConfig({
+ host: config.host,
+ namespace: config.namespace,
+});
+
+loadInitializers();
diff --git a/app/components/forms/forgot-password.gts b/app/components/forms/forgot-password.gts
index 7ed7fb9..c314f7a 100644
--- a/app/components/forms/forgot-password.gts
+++ b/app/components/forms/forgot-password.gts
@@ -2,8 +2,7 @@ import { LinkTo } from '@ember/routing';
import InputsValidationComponent from 'ember-boilerplate/components/inputs/input-validation';
import t from 'ember-intl/helpers/t';
-
-import YupForm from './yup-form';
+import TpkForm from '@triptyk/ember-input-validation/components/tpk-form';
import type { TOC } from '@ember/component/template-only';
import type { ForgotPasswordChangeset } from 'ember-boilerplate/changesets/forgot-password';
@@ -22,7 +21,7 @@ export interface FormsForgotPasswordSignature {
}
const FormsForgotPassword: TOC =
- =
...attributes
>
=
{{t "components.forms.forgot-password.cancel"}}
-
+
;
export default FormsForgotPassword;
diff --git a/app/components/forms/login.gts b/app/components/forms/login.gts
index f369bab..ddb6c00 100644
--- a/app/components/forms/login.gts
+++ b/app/components/forms/login.gts
@@ -1,13 +1,10 @@
-import { LinkTo } from '@ember/routing';
-
-import InputsValidationComponent from 'ember-boilerplate/components/inputs/input-validation';
-import t from 'ember-intl/helpers/t';
-
-import YupForm from './yup-form';
-
+import TpkForm from '@triptyk/ember-input-validation/components/tpk-form';
import type { TOC } from '@ember/component/template-only';
import type { LoginChangeset } from 'ember-boilerplate/changesets/login';
import type { Schema } from 'yup';
+import t from 'ember-intl/helpers/t';
+import { LinkTo } from '@ember/routing';
+import InputsValidationComponent from '../inputs/input-validation';
export interface FormsLoginSignature {
Args: {
@@ -22,7 +19,7 @@ export interface FormsLoginSignature {
}
const FormsLogin: TOC =
- =
data-test-form-login
>
=
data-test-input="email"
/>
=
-
+
;
export default FormsLogin;
diff --git a/app/components/forms/password-recovery.gts b/app/components/forms/password-recovery.gts
index 96577c9..33f6f08 100644
--- a/app/components/forms/password-recovery.gts
+++ b/app/components/forms/password-recovery.gts
@@ -1,11 +1,9 @@
-import InputsValidationComponent from 'ember-boilerplate/components/inputs/input-validation';
-import t from 'ember-intl/helpers/t';
-
-import YupForm from './yup-form';
-
import type { TOC } from '@ember/component/template-only';
import type { ResetPasswordChangeset } from 'ember-boilerplate/changesets/reset-password';
import type { Schema } from 'yup';
+import TpkForm from '@triptyk/ember-input-validation/components/tpk-form';
+import t from 'ember-intl/helpers/t';
+import InputsValidationComponent from '../inputs/input-validation';
export interface FormsPasswordRecoveryComponentSignature {
Args: {
@@ -20,7 +18,7 @@ export interface FormsPasswordRecoveryComponentSignature {
}
const FormsPasswordRecovery: TOC =
- =
=
=
-
+
;
export default FormsPasswordRecovery;
diff --git a/app/components/forms/register.gts b/app/components/forms/register.gts
index 95b3c94..bf19822 100644
--- a/app/components/forms/register.gts
+++ b/app/components/forms/register.gts
@@ -1,13 +1,11 @@
import Component from '@glimmer/component';
import { hash } from '@ember/helper';
-
-import InputsValidationComponent from 'ember-boilerplate/components/inputs/input-validation';
import t from 'ember-intl/helpers/t';
-import YupForm from './yup-form';
-
import type { RegisterChangeset } from 'ember-boilerplate/changesets/register';
import type { Schema } from 'yup';
+import TpkForm from '@triptyk/ember-input-validation/components/tpk-form';
+import InputsValidationComponent from '../inputs/input-validation';
export interface FormsRegisterSignature {
Args: {
@@ -21,7 +19,7 @@ export interface FormsRegisterSignature {
Element: HTMLFormElement;
}
-export default class FormsRegister extends Component {
+export default class RegisterForm extends Component {
maskForEuro = {
mask: 'num €',
lazy: false,
@@ -38,7 +36,7 @@ export default class FormsRegister extends Component {
};
- {
>
{
/>
{
/>
{
/>
{
/>
{
/>
{
/>
{
{{t "global.submit"}}
-
+
}
diff --git a/app/components/forms/yup-form.gts b/app/components/forms/yup-form.gts
deleted file mode 100644
index 4146a2a..0000000
--- a/app/components/forms/yup-form.gts
+++ /dev/null
@@ -1,99 +0,0 @@
-import Component from '@glimmer/component';
-import { assert } from '@ember/debug';
-import { on } from '@ember/modifier';
-
-import scrollOnError from 'ember-boilerplate/modifiers/scroll-on-error';
-import {
- validateAndMapErrors,
- validateOneAndMapErrors,
-} from 'ember-boilerplate/utils/validate-and-map';
-import { task } from 'ember-concurrency';
-import perform from 'ember-concurrency/helpers/perform';
-import { ImmerChangeset, isChangeset } from 'ember-immer-changeset';
-import { Schema } from 'yup';
-
-import type Owner from '@ember/owner';
-import type { Promisable } from 'type-fest';
-
-function isFieldError(field: string, errorKey: string): boolean {
- const regex = new RegExp(`^${field.replaceAll('.', '\\.')}($|\\.|\\[)`);
-
- return regex.test(errorKey);
-}
-
-export interface ChangesetFormComponentSignature {
- Args: {
- changeset: ImmerChangeset;
- onSubmit: >(changeset: T) => Promisable;
- validationSchema: Schema;
- removeErrorsOnSubmit?: boolean;
- executeOnValid?: boolean;
- };
- Blocks: {
- default: [ChangesetFormComponent['validateAndSubmit']];
- };
- Element: HTMLFormElement;
-}
-
-export default class ChangesetFormComponent extends Component {
- public constructor(owner: Owner, args: ChangesetFormComponentSignature['Args']) {
- super(owner, args);
- assert(
- '@changeset is required and must be an ImmerChangeset',
- isChangeset(args.changeset) && args.changeset instanceof ImmerChangeset
- );
- assert('@onSubmit is required', typeof args.onSubmit === 'function');
- assert('@validationSchema is required', args.validationSchema instanceof Schema);
-
- this.args.changeset.onSet(async (key) => {
- await this.args.changeset.validate(async (draft) => {
- const errors = await validateOneAndMapErrors(key, this.args.validationSchema, draft);
-
- for (const error of this.args.changeset.errors) {
- if (isFieldError(key, error.key)) {
- this.args.changeset.removeError(error.key);
- }
- }
-
- for (const error of errors) {
- this.args.changeset.addError(error);
- }
- });
- });
- }
-
- validateAndSubmit = task(this, { drop: true }, async () => {
- if (this.args.removeErrorsOnSubmit ?? true) {
- this.args.changeset.removeErrors();
- }
-
- await this.args.changeset.validate(async (dto) => {
- const errors = await validateAndMapErrors(this.args.validationSchema, dto);
-
- for (const error of errors) {
- this.args.changeset.addError(error);
- }
- });
-
- if (!this.args.changeset.isValid) {
- return;
- }
-
- if (this.args.executeOnValid ?? true) {
- this.args.changeset.execute();
- }
-
- await this.args.onSubmit(this.args.changeset);
- });
-
- submit = task(this, async (e: Event) => {
- e.preventDefault();
- await this.validateAndSubmit.perform();
- });
-
-
-
-
-}
diff --git a/app/components/inputs/cancel-button.gts b/app/components/inputs/cancel-button.gts
index 1d5c1de..510cf58 100644
--- a/app/components/inputs/cancel-button.gts
+++ b/app/components/inputs/cancel-button.gts
@@ -2,8 +2,6 @@ import Component from '@glimmer/component';
import { on } from '@ember/modifier';
import { action } from '@ember/object';
import { service } from '@ember/service';
-
-import windowHistoryBack from '@triptyk/ember-utils/utils/window-history-back';
import t from 'ember-intl/helpers/t';
import type RouterService from '@ember/routing/router-service';
@@ -31,7 +29,7 @@ export default class CancelButtonComponent extends Component
diff --git a/app/handlers/users.ts b/app/handlers/users.ts
index f05ec18..58c2b17 100644
--- a/app/handlers/users.ts
+++ b/app/handlers/users.ts
@@ -13,15 +13,15 @@ export const usersHandlers= [
http.get('http://localhost:8080/api/v1/users/profile', () => {
return HttpResponse.json({
data: {
- type: 'users',
id: '1',
+ type: 'user',
attributes: {
email: '',
firstName: '',
lastName: '',
phone: '',
role: '',
- },
+ }
},
});
}),
@@ -33,8 +33,9 @@ export const usersHandlers= [
http.post('http://localhost:8080/api/v1/users', () => {
return HttpResponse.json({
data: {
- type: 'users',
- attributes: {}
+ attributes: {},
+ type: 'user',
+ id: Math.floor(Math.random() * 1000).toString(),
}
});
})
diff --git a/app/index.html b/app/index.html
index e05747f..1cd23e2 100644
--- a/app/index.html
+++ b/app/index.html
@@ -1,16 +1,13 @@
-
+
- EmberBoilerplate
+ Ember boilerplate
{{content-for "head"}}
-
-
-
;
-}
+ @attr('string') declare email: string;
+ @attr('string') declare firstName: string;
+ @attr('string') declare lastName: string;
+ @attr('string') declare phone: string;
+ @attr('string') declare role: string;
+ @attr('string') declare password: string;
-declare module 'ember-data/types/registries/model' {
- export default interface ModelRegistry {
- user: UserModel;
- }
+ [ResourceType] = 'user' as const;
}
diff --git a/app/modifiers/errors-scroll-up.ts b/app/modifiers/errors-scroll-up.ts
index 45dadf9..3e53407 100644
--- a/app/modifiers/errors-scroll-up.ts
+++ b/app/modifiers/errors-scroll-up.ts
@@ -1,4 +1,3 @@
-import { later } from '@ember/runloop';
import { runTask } from 'ember-lifeline';
import type { ValidationError } from 'ember-immer-changeset';
diff --git a/app/routes/application.ts b/app/routes/application.ts
index b1b1806..ca91278 100644
--- a/app/routes/application.ts
+++ b/app/routes/application.ts
@@ -1,13 +1,13 @@
import { registerDestructor } from '@ember/destroyable';
import Route from '@ember/routing/route';
import { service } from '@ember/service';
-
import config from 'ember-boilerplate/config/environment';
import { usersHandlers } from 'ember-boilerplate/handlers/users';
import type CurrentUserService from 'ember-boilerplate/services/current-user';
import type { IntlService } from 'ember-intl';
import type SessionService from 'ember-simple-auth/services/session';
+import { setupWorker } from 'msw/browser';
export default class Application extends Route {
@service declare session: SessionService;
@@ -29,10 +29,7 @@ export default class Application extends Route {
}
async function setupMSW(context: object) {
- // @ts-expect-error rewritten
- let { default: setupServer } = await import('/setup-worker.js');
-
- const server = setupServer(usersHandlers);
+ const server = setupWorker(...usersHandlers);
await server.start();
diff --git a/app/serializers/application.ts b/app/serializers/application.ts
deleted file mode 100644
index d6e81cd..0000000
--- a/app/serializers/application.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import JSONAPISerializer from '@ember-data/serializer/json-api';
-
-import type { Snapshot } from '@ember-data/store';
-
-export default class Application extends JSONAPISerializer {
- serializeAttribute(
- snapshot: Snapshot,
- json: Record,
- key: string,
- attributes: Record
- ) {
- if (snapshot.record.get('isNew') || snapshot.changedAttributes()[key]) {
- super.serializeAttribute(snapshot, json, key, attributes);
- }
- }
-
- keyForAttribute(key: string) {
- return key;
- }
-
- keyForRelationship(key: string) {
- return key;
- }
-}
-
-// DO NOT DELETE: this is how TypeScript knows how to look up your serializers.
-declare module 'ember-data/types/registries/serializer' {
- export default interface SerializerRegistry {
- application: Application;
- }
-}
diff --git a/app/services/auth-handler.ts b/app/services/auth-handler.ts
new file mode 100644
index 0000000..22722e5
--- /dev/null
+++ b/app/services/auth-handler.ts
@@ -0,0 +1,17 @@
+import {service} from "@ember/service";
+import type SessionService from "./session";
+import type { NextFn, RequestContext } from "@ember-data/request";
+
+export default class AuthHandler {
+ @service declare session: SessionService;
+
+ request(context: RequestContext, next: NextFn) {
+ const headers = new Headers(context.request.headers);
+ headers.append(
+ 'Authorization',
+ `Bearer ${this.session.data.authenticated.accessToken}`,
+ );
+
+ return next(Object.assign({}, context.request, { headers }));
+ }
+}
diff --git a/app/services/changesets/changeset-service.ts b/app/services/changesets/changeset-service.ts
deleted file mode 100644
index bf61df3..0000000
--- a/app/services/changesets/changeset-service.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { Changeset } from 'ember-immer-changeset';
-import type { Promisable } from 'type-fest';
-
-export interface ChangesetService {
- save(changeset: T): Promisable;
-}
diff --git a/app/services/changesets/register.ts b/app/services/changesets/register.ts
index 8cef921..9d99cde 100644
--- a/app/services/changesets/register.ts
+++ b/app/services/changesets/register.ts
@@ -1,39 +1,45 @@
import Service from '@ember/service';
import { service } from '@ember/service';
-
-import type { ChangesetService } from './changeset-service';
-import type StoreService from '@ember-data/store';
import type { RegisterChangeset } from 'ember-boilerplate/changesets/register';
+import { Result, type Err } from 'true-myth/result';
+import type Store from '@ember-data/store';
import type UserModel from 'ember-boilerplate/models/user';
+import type SafeStore from '../safe-store';
+import { createRecord } from '@ember-data/json-api/request';
+import type { Document } from '@ember-data/store/-private/document';
export default class RegisterChangesetService
extends Service
- implements ChangesetService
{
- @service declare store: StoreService;
+ @service declare safeStore: SafeStore;
+ @service declare store: Store;
- async save(changeset: RegisterChangeset): Promise {
+ async save(changeset: RegisterChangeset): Promise> {
changeset.execute();
const changesetData = changeset.data;
- const user = this.store.createRecord('user', {
- lastName: changesetData.lastName,
- firstName: changesetData.firstName,
- phone: changesetData.phone,
- email: changesetData.email,
- password: changesetData.password,
- role: 'user',
+ let user = this.store.createRecord('user', {
+ ...changesetData,
});
- await user.save();
+ let response = await this.safeStore.request>(createRecord(user) as never);
- return user;
- }
-}
+ if (response.isErr) {
+ changeset.unexecute();
+ for (const iterator of response.error.errors) {
+ changeset.addError({
+ key: iterator.source.pointer,
+ message: iterator.detail,
+ value: changeset.get(iterator.source.pointer.split('/')[1]),
+ originalValue: changeset.get(iterator.source.pointer.split('/')[1]),
+ })
+ }
+ return response as Err;
+ }
+
+ await changeset.save();
-declare module '@ember/service' {
- interface Registry {
- 'changesets/register': RegisterChangesetService;
+ return response.map((user) => user.content.data!);
}
}
diff --git a/app/services/current-user.ts b/app/services/current-user.ts
index a5d26a2..0bacc6d 100644
--- a/app/services/current-user.ts
+++ b/app/services/current-user.ts
@@ -3,31 +3,30 @@ import Service from '@ember/service';
import { service } from '@ember/service';
import type SessionService from './session';
-import type Store from '@ember-data/store';
-import type FetchService from '@triptyk/ember-utils/services/fetch';
+import type RequestManager from '@ember-data/request';
import type UserModel from 'ember-boilerplate/models/user';
+import type SafeStore from './safe-store';
+import { findRecord } from '@ember-data/json-api/request';
+import { Maybe } from 'true-myth';
export default class CurrentUserService extends Service {
@service declare session: SessionService;
- @service declare store: Store;
- @service declare fetch: FetchService;
+ @service declare requestManager: RequestManager;
+ @service declare safeStore: SafeStore;
- @tracked public user?: UserModel;
+ @tracked public user: Maybe = Maybe.nothing();
async load() {
if (this.session.isAuthenticated) {
- let user = await this.store.queryRecord('user', { profile: true });
+ let userResponse = await this.safeStore.request(findRecord('user', 'profile'))
- this.user = user;
+ if (userResponse.isErr) {
+ return null;
+ }
+
+ this.user = Maybe.just(userResponse.value.content.data);
}
return this.user;
}
}
-
-// DO NOT DELETE: this is how TypeScript knows how to look up your services.
-declare module '@ember/service' {
- interface Registry {
- 'current-user': CurrentUserService;
- }
-}
diff --git a/app/services/error-handler.ts b/app/services/error-handler.ts
index d203c76..be17113 100644
--- a/app/services/error-handler.ts
+++ b/app/services/error-handler.ts
@@ -11,7 +11,7 @@ export interface TranslatedErrors {
export default class ErrorHandlerService extends Service {
@service declare flashMessages: FlashMessageService;
- public handle(changeset: Changeset, errors: Error[] | string) {
+ public handle(errors: Error[] | string) {
this.flashMessages.danger(`${errors}`);
}
}
diff --git a/app/services/head-data.ts b/app/services/head-data.ts
index b717ecb..c0b86a9 100644
--- a/app/services/head-data.ts
+++ b/app/services/head-data.ts
@@ -11,11 +11,3 @@ export default class HeadDataService extends Service {
return config.applicationName;
}
}
-
-// DO NOT DELETE: this is how TypeScript knows how to look up your services.
-declare module '@ember/service' {
- // eslint-disable-next-line no-unused-vars
- interface Registry {
- 'head-data': HeadDataService;
- }
-}
diff --git a/app/services/request-manager.ts b/app/services/request-manager.ts
new file mode 100644
index 0000000..5fd9156
--- /dev/null
+++ b/app/services/request-manager.ts
@@ -0,0 +1,14 @@
+import RequestManager, { type Handler,type NextFn,type RequestContext,type RequestInfo } from "@ember-data/request";
+import { setOwner } from "@ember/owner";
+import { getOwner } from "@ember/owner";
+import AuthHandler from "./auth-handler";
+import Fetch from "@ember-data/request/fetch";
+
+export default class extends RequestManager {
+ constructor(args: {}) {
+ super(args);
+ const authHandler = new AuthHandler();
+ setOwner(authHandler, getOwner(this)!);
+ this.use([authHandler, Fetch]);
+ }
+}
diff --git a/app/services/safe-store.ts b/app/services/safe-store.ts
new file mode 100644
index 0000000..7f8bde2
--- /dev/null
+++ b/app/services/safe-store.ts
@@ -0,0 +1,15 @@
+import type { Future, StructuredDataDocument } from "@ember-data/request";
+import type { StoreRequestInput } from "@ember-data/store";
+import type Store from "@ember-data/store";
+import {service} from "@ember/service";
+import Service from "@ember/service";
+import { toResult } from "ember-boilerplate/utils/result-utils";
+import type Result from "true-myth/result";
+
+export default class SafeStore extends Service {
+ @service declare store: Store;
+
+ request