-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into remove-my-favorites
- Loading branch information
Showing
17 changed files
with
296 additions
and
160 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
git-format-staged -f 'prettier --ignore-unknown --stdin --stdin-filepath "{}"' . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# Coding Guidelines | ||
|
||
|
||
## Inject() over Constructor-Injections | ||
|
||
Use `inject()` instead of constuctor-injection to make the code more explicit and obvious. | ||
|
||
```typescript | ||
// use | ||
myService = inject(MyService); | ||
|
||
// instead of | ||
constructor( | ||
private myservice: MyService | ||
) {} | ||
``` | ||
|
||
## RxJS | ||
Leverage RxJS for API calls, web sockets, and complex data flows, especially when handling multiple asynchronous events. Combine with Signals to simplify component state management. | ||
|
||
## Signals | ||
|
||
Use signals for changing values in Components | ||
|
||
### Signal from Observable | ||
|
||
#### GET | ||
|
||
Make API-requests with observables and expose the state as a signal: | ||
|
||
```typescript | ||
// retrive data from API using RxJS | ||
private users$ = this.http.get<User[]>(this.userUrl); | ||
|
||
// expose as signal | ||
users = toSignal(this.users$, { initialValue: [] as User[]}); | ||
``` | ||
|
||
The observable `users$` is just used to pass the state to the _readonly_ signal `users`. (The signals created from an observable are always _readonly_!) | ||
No need for unsubscription - this is handled by `toSignal()` automatically. | ||
|
||
Use the signal in the component not in the template. (Separation of concerns) | ||
|
||
#### POST / DELETE etc. | ||
|
||
To update data in a signal you have to create a WritableSignal: | ||
|
||
```typescript | ||
// WritableSignal | ||
users = signal<Tag[]>([]); | ||
// retrive data from API using RxJS and write it in the WritableSignal | ||
private users$ = this.http.get<User[]>(this.userUrl).pipe(tap((users) => this.users.set(users))); | ||
// only used to automatically un-/subscribe to the observable | ||
readOnlyUsers = toSignal(this.users$, { initialValue: [] as User[]}); | ||
``` | ||
|
||
## Auth Service | ||
|
||
The frontend provides a singelton auth-service which holds all restrictions for the current user. | ||
|
||
After injecting the service in your component you can get Permissions/Actions depending on your needs: | ||
|
||
```typescript | ||
// inject the service | ||
authService = inject(AuthService); | ||
|
||
// get actions for a specific permission | ||
const actions = this.authService.getActionsForPermission('MY_PERMISSION'); | ||
|
||
// verify role in an action and set signal | ||
this.canCreate.set(actions.some(isAllowed('CREATE'))); | ||
|
||
// or directly set signal based on a concret permission and action value | ||
this.canViewSettings.set(this.authService.hasPermission('SETTINGS', 'READ')); | ||
|
||
``` |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,82 +1,109 @@ | ||
import { TestBed } from '@angular/core/testing'; | ||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; | ||
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; | ||
import { AuthService } from './auth.service'; | ||
import { provideHttpClient } from '@angular/common/http'; | ||
|
||
describe('AuthService', () => { | ||
let authService: AuthService; | ||
let httpTestingController: HttpTestingController; | ||
let API_URL: string; | ||
|
||
beforeEach(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [HttpClientTestingModule], | ||
providers: [AuthService], | ||
imports: [], | ||
providers: [AuthService, provideHttpClient(), provideHttpClientTesting()], | ||
}); | ||
authService = TestBed.inject(AuthService); | ||
httpTestingController = TestBed.inject(HttpTestingController); | ||
API_URL = `${authService.getBaseUrl()}/permissions/restrictions/ownRestrictions/`; | ||
}); | ||
|
||
afterEach(() => { | ||
httpTestingController.verify(); | ||
}); | ||
|
||
it('should return true if the user has the specified permission and action', () => { | ||
it('should reload permissions', () => { | ||
authService.refreshData(); | ||
const requests = httpTestingController.match(API_URL); | ||
expect(requests.length).toEqual(2); | ||
}); | ||
|
||
it('should return actions for a permission', () => { | ||
const permissionName = 'examplePermission'; | ||
const CREATE = 'CREATE'; | ||
const READ = 'READ'; | ||
|
||
const req = httpTestingController.expectOne(API_URL); | ||
expect(req.request.method).toBe('GET'); | ||
req.flush([ | ||
{ permission: { name: permissionName }, action: READ }, | ||
{ permission: { name: permissionName }, action: CREATE }, | ||
{ permission: { name: 'secondPermission' }, action: CREATE }, | ||
{ permission: { name: 'thirdPermission' }, action: CREATE }, | ||
]); | ||
|
||
expect(authService.getActionsForPermission(permissionName)).toEqual([READ, CREATE]); | ||
}); | ||
|
||
it('should not return actions for a missing permission', () => { | ||
const permissionName = 'examplePermission'; | ||
const action = 'CREATE'; | ||
|
||
authService.hasPermission(permissionName, action).subscribe((result) => { | ||
expect(result).toBeTrue(); | ||
}); | ||
const req = httpTestingController.expectOne(API_URL); | ||
expect(req.request.method).toBe('GET'); | ||
req.flush([ | ||
{ permission: { name: 'firstPermiison' }, action: action }, | ||
{ permission: { name: 'secondPermission' }, action: action }, | ||
{ permission: { name: 'thirdPermission' }, action: action }, | ||
]); | ||
|
||
const req = httpTestingController.expectOne(`${authService.getBaseUrl()}/permissions/restrictions/ownRestrictions/`); | ||
expect(authService.getActionsForPermission(permissionName)).toEqual([]); | ||
}); | ||
|
||
it('should return true if the user has the specified permission and action', () => { | ||
const permissionName = 'examplePermission'; | ||
const action = 'CREATE'; | ||
|
||
const req = httpTestingController.expectOne(API_URL); | ||
expect(req.request.method).toBe('GET'); | ||
req.flush([ | ||
{ permission: { name: permissionName }, action: 'READ' }, | ||
{ permission: { name: permissionName }, action: action }, | ||
]); | ||
|
||
expect(authService.hasPermission(permissionName, action)).toBeTrue(); | ||
}); | ||
|
||
it('should return true if the user has the specified permission and ALL action', () => { | ||
const permissionName = 'examplePermission'; | ||
const action = 'CREATE'; | ||
|
||
authService.hasPermission(permissionName, action).subscribe((result) => { | ||
expect(result).toBeTrue(); | ||
}); | ||
|
||
const req = httpTestingController.expectOne(`${authService.getBaseUrl()}/permissions/restrictions/ownRestrictions/`); | ||
const req = httpTestingController.expectOne(API_URL); | ||
expect(req.request.method).toBe('GET'); | ||
req.flush([ | ||
{ permission: { name: permissionName }, action: 'ALL' } | ||
]); | ||
req.flush([{ permission: { name: permissionName }, action: 'ALL' }]); | ||
|
||
expect(authService.hasPermission(permissionName, action)).toBeTrue(); | ||
}); | ||
|
||
it('should return false if the user has the specified permission and but not action', () => { | ||
const permissionName = 'examplePermission'; | ||
const action = 'CREATE'; | ||
|
||
authService.hasPermission(permissionName, action).subscribe((result) => { | ||
expect(result).toBeFalse(); | ||
}); | ||
|
||
const req = httpTestingController.expectOne(`${authService.getBaseUrl()}/permissions/restrictions/ownRestrictions/`); | ||
const req = httpTestingController.expectOne(API_URL); | ||
expect(req.request.method).toBe('GET'); | ||
req.flush([ | ||
{ permission: { name: permissionName }, action: 'READ' } | ||
]); | ||
req.flush([{ permission: { name: permissionName }, action: 'READ' }]); | ||
|
||
expect(authService.hasPermission(permissionName, action)).toBeFalse(); | ||
}); | ||
|
||
it("should return false if the user doesn't have the specified permission", () => { | ||
const permissionName = 'examplePermission'; | ||
const action = 'CREATE'; | ||
|
||
authService.hasPermission(permissionName, action).subscribe((result) => { | ||
expect(result).toBeFalse(); | ||
}); | ||
|
||
const req = httpTestingController.expectOne(`${authService.getBaseUrl()}/permissions/restrictions/ownRestrictions/`); | ||
const req = httpTestingController.expectOne(API_URL); | ||
expect(req.request.method).toBe('GET'); | ||
req.flush([ | ||
{ permission: { name: "otherPermission" }, action: 'READ' } | ||
]); | ||
req.flush([{ permission: { name: 'otherPermission' }, action: 'READ' }]); | ||
|
||
expect(authService.hasPermission(permissionName, action)).toBeFalse(); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.