Skip to content

Commit

Permalink
Fixed build issues
Browse files Browse the repository at this point in the history
  • Loading branch information
fancyDevelopment committed Sep 10, 2024
1 parent 835372a commit 38613bc
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 47 deletions.
8 changes: 4 additions & 4 deletions libs/ngrx-hateoas/src/lib/provide.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TestBed } from "@angular/core/testing";
import { AntiForgeryOptions, CustomHeadersOptions, HATEOAS_ANTI_FORGERY, HATEOAS_CUSTOM_HEADERS, HATEOAS_LOGIN_REDIRECT, HATEOAS_METADATA_PROVIDER, LoginRedirectOptions, MetadataProvider, provideHateoas, withAntiForgery, withCustomHeaders, withLoginRedirect, withMetadataProvider } from "./provide";
import { DynamicResource, DynamicResourceValue, ResourceAction, ResourceLink, ResourceSocket } from "./models";
import { ResourceAction, ResourceLink, ResourceSocket } from "./models";

describe('provideHateaos', () => {

Expand Down Expand Up @@ -68,13 +68,13 @@ describe('provideHateaos', () => {

const dummyMetadataProvider: MetadataProvider = {
linkLookup(resource, linkName) {
return { href: (resource as any)['myMeta'][`_link_${linkName}`] } satisfies ResourceLink;
return { href: (resource as Record<string, Record<string, string>>)['myMeta'][`_link_${linkName}`] } satisfies ResourceLink;
},
actionLookup(resource, actionName) {
return { href: (resource as any)['myMeta'][`_action_${actionName}`], method: 'PUT' } satisfies ResourceAction;
return { href: (resource as Record<string, Record<string, string>>)['myMeta'][`_action_${actionName}`], method: 'PUT' } satisfies ResourceAction;
},
socketLookup(resource, socketName) {
return { href: (resource as any)['myMeta'][`_socket_${socketName}`], method: 'update' } satisfies ResourceSocket;
return { href: (resource as Record<string, Record<string, string>>)['myMeta'][`_socket_${socketName}`], method: 'update' } satisfies ResourceSocket;
}
}

Expand Down
36 changes: 29 additions & 7 deletions libs/ngrx-hateoas/src/lib/provide.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EnvironmentProviders, InjectionToken, makeEnvironmentProviders, Provider } from "@angular/core";
import { HateoasService } from "./services/hateoas.service";
import { RequestService } from "./services/request.service";
import { DynamicResource, Resource, ResourceAction, ResourceLink, ResourceSocket } from "./models";
import { Resource, ResourceAction, ResourceLink, ResourceSocket } from "./models";

export enum HateoasFeatureKind {
AntiForgery,
Expand Down Expand Up @@ -49,30 +49,52 @@ export interface MetadataProvider {
socketLookup(resource: unknown, socketName: string): ResourceSocket | undefined;
}

function isResource(resource: unknown): resource is DynamicResource {
function isResource(resource: unknown): resource is Resource {
return typeof resource === 'object' && resource !== null;
}

function isResourceLinkCollection(resourceLinks: unknown): resourceLinks is Record<string, ResourceLink> {
function isResourceLinkRecord(resourceLinks: unknown): resourceLinks is Record<string, ResourceLink> {
return typeof resourceLinks === 'object' && resourceLinks !== null;
}

function isResourceActionRecord(resourceActions: unknown): resourceActions is Record<string, ResourceAction> {
return typeof resourceActions === 'object' && resourceActions !== null;
}

function isResourceSocketRecord(resourceSockets: unknown): resourceSockets is Record<string, ResourceSocket> {
return typeof resourceSockets === 'object' && resourceSockets !== null;
}

function isResourceLink(resourceLink: unknown): resourceLink is ResourceLink {
return typeof resourceLink === 'object' && resourceLink !== null && 'href' in resourceLink;
return typeof resourceLink === 'object' && resourceLink !== null && 'href' in resourceLink;
}

function isResourceAction(resourceAction: unknown): resourceAction is ResourceAction {
return typeof resourceAction === 'object' && resourceAction !== null && 'href' in resourceAction && 'method' in resourceAction;
}

function isResourceSocket(resourceSocket: unknown): resourceSocket is ResourceSocket {
return typeof resourceSocket === 'object' && resourceSocket !== null && 'href' in resourceSocket && 'method' in resourceSocket;
}

const defaultMetadataProvider: MetadataProvider = {
linkLookup(resource: unknown, linkName: string): ResourceLink | undefined {
if(isResource(resource) && isResourceLinkCollection(resource['_links'] && isResourceLink(resource['_links'][linkName])))
if(isResource(resource) && isResourceLinkRecord(resource['_links']) && isResourceLink(resource['_links'][linkName]))
return resource['_links'][linkName];
else
return undefined;
},
actionLookup(resource: unknown, actionName: string): ResourceAction | undefined {
return (resource as any)?._actions?.[actionName];
if(isResource(resource) && isResourceActionRecord(resource['_actions']) && isResourceAction(resource['_actions'][actionName]))
return resource['_actions'][actionName];
else
return undefined;
},
socketLookup(resource: unknown, socketName: string): ResourceSocket | undefined {
return (resource as any)?._sockets?.[socketName];
if(isResource(resource) && isResourceSocketRecord(resource['_sockets']) && isResourceSocket(resource['_sockets'][socketName]))
return resource['_sockets'][socketName];
else
return undefined;
}
}

Expand Down
6 changes: 3 additions & 3 deletions libs/ngrx-hateoas/src/lib/services/hateoas.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { HATEOAS_METADATA_PROVIDER, MetadataProvider } from '../provide';

const dummyHateoasMetadataProvider: MetadataProvider = {
linkLookup(resource, linkName) {
return { href: (resource as any)['myMeta'][`_link_${linkName}`] } satisfies ResourceLink;
return { href: (resource as Record<string, Record<string, string>>)['myMeta'][`_link_${linkName}`] } satisfies ResourceLink;
},
actionLookup(resource, actionName) {
return { href: (resource as any)['myMeta'][`_action_${actionName}`], method: 'PUT' } satisfies ResourceAction;
return { href: (resource as Record<string, Record<string, string>>)['myMeta'][`_action_${actionName}`], method: 'PUT' } satisfies ResourceAction;
},
socketLookup(resource, socketName) {
return { href: (resource as any)['myMeta'][`_socket_${socketName}`], method: 'update' } satisfies ResourceSocket;
return { href: (resource as Record<string, Record<string, string>>)['myMeta'][`_socket_${socketName}`], method: 'update' } satisfies ResourceSocket;
}
}

Expand Down
40 changes: 28 additions & 12 deletions libs/ngrx-hateoas/src/lib/services/request.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,39 +165,55 @@ describe('RequestService', () => {
const serverRequest = httpTestingController.expectOne('/api/test');
expect(serverRequest.request.method).toBe('GET');
serverRequest.flush(null, { status: 401, statusText: 'Unauthorized' });
const clientRequest = await clientRequestPromise;
expect(clientRequest).toBeUndefined();
expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute');
try {
await clientRequestPromise;
} catch {
expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute');
return;
}
expect(false).toBeTrue();
});

it('makes PUT requests correctly', async () => {
const clientRequestPromise = requestService.request('PUT', '/api/test', { strProp: 'foo'});
const serverRequest = httpTestingController.expectOne('/api/test');
expect(serverRequest.request.method).toBe('PUT');
serverRequest.flush(null, { status: 401, statusText: 'Unauthorized' });
const clientRequest = await clientRequestPromise;
expect(clientRequest).toBeUndefined();
expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute');
try {
await clientRequestPromise;
} catch {
expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute');
return;
}
expect(false).toBeTrue();
});

it('makes POST requests correctly', async () => {
const clientRequestPromise = requestService.request('POST', '/api/test', { strProp: 'foo'});
const serverRequest = httpTestingController.expectOne('/api/test');
expect(serverRequest.request.method).toBe('POST');
serverRequest.flush(null, { status: 401, statusText: 'Unauthorized' });
const clientRequest = await clientRequestPromise;
expect(clientRequest).toBeUndefined();
expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute');
try {
await clientRequestPromise;
} catch {
expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute');
return;
}
expect(false).toBeTrue();
});

it('makes DELETE requests correctly', async () => {
const clientRequestPromise = requestService.request('DELETE', '/api/test');
const serverRequest = httpTestingController.expectOne('/api/test');
expect(serverRequest.request.method).toBe('DELETE');
serverRequest.flush(null, { status: 401, statusText: 'Unauthorized' });
const clientRequest = await clientRequestPromise;
expect(clientRequest).toBeUndefined();
expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute');
try {
await clientRequestPromise;
} catch {
expect(currentLocation).toBe('/login-path?redirect-url=%2Fangular%2Froute');
return;
}
expect(true).toBe(false);
});
});

Expand Down
6 changes: 2 additions & 4 deletions libs/ngrx-hateoas/src/lib/services/request.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class RequestService {

private httpClient = inject(HttpClient);

public async request<T>(method: 'GET' | 'PUT' | 'POST' | 'DELETE', url: string, body?: unknown): Promise<T | undefined> {
public async request<T>(method: 'GET' | 'PUT' | 'POST' | 'DELETE', url: string, body?: unknown): Promise<T> {
let headers = new HttpHeaders().set('Content-Type', 'application/json');

if(this.customHeadersOptions) {
Expand All @@ -38,10 +38,8 @@ export class RequestService {
// Redirect to sign in
const currentUrl = this.window.location.href;
this.window.location.href = `${this.loginRedirectOptions.loginUrl}?${this.loginRedirectOptions.redirectUrlParamName}=` + encodeURIComponent(currentUrl);
return undefined;
} else {
throw errorResponse;
}
throw errorResponse;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ import { DeepPatchableSignal, toDeepPatchableSignal } from "../util/deep-patchab
import { RequestService } from "../services/request.service";
import { HateoasService } from "../services/hateoas.service";

export type ResourceStateProps<TResource> = {
url: string,
isLoading: boolean,
isAvailable: boolean,
initiallyLoaded: boolean,
resource: TResource
}

export type LinkedHypermediaResourceState<ResourceName extends string, TResource> =
{
[K in ResourceName]: {
url: string,
isLoading: boolean,
isAvailable: boolean,
initiallyLoaded: boolean,
resource: TResource
}
[K in ResourceName]: ResourceStateProps<TResource>
};

export type ConnectLinkedHypermediaResourceMethod<ResourceName extends string> = {
Expand Down Expand Up @@ -52,6 +54,10 @@ type linkedRxInput = {
linkName: string
}

function getState<TResource>(store: unknown, stateKey: string): ResourceStateProps<TResource> {
return (store as Record<string, Signal<ResourceStateProps<TResource>>>)[stateKey]()
}

export function withLinkedHypermediaResource<ResourceName extends string, TResource>(
resourceName: ResourceName, initialValue: TResource): SignalStoreFeature<
{ state: object; computed: Record<string, Signal<unknown>>; methods: Record<string, Function> },
Expand All @@ -70,7 +76,7 @@ export function withLinkedHypermediaResource<ResourceName extends string, TResou

return signalStoreFeature(
withState({
[resourceName]: {
[stateKey]: {
url: '',
isLoading: false,
isAvailable: false,
Expand All @@ -87,30 +93,30 @@ export function withLinkedHypermediaResource<ResourceName extends string, TResou
map(input => hateoasService.getLink(input.resource, input.linkName)?.href),
filter(href => isValidHref(href)),
map(href => href!),
filter(href => store[stateKey].url() !== href),
tap(href => patchState(store, { [stateKey]: { ...store[stateKey](), url: href, isLoading: true, isAvailable: true } })),
filter(href => getState(store, stateKey).url !== href),
tap(href => patchState(store, { [stateKey]: { ...getState<TResource>(store, stateKey), url: href, isLoading: true, isAvailable: true } })),
switchMap(href => requestService.request<TResource>('GET', href)),
tap(resource => patchState(store, { [stateKey]: { ...store[stateKey](), resource, isLoading: false, initiallyLoaded: true } }))
tap(resource => patchState(store, { [stateKey]: { ...getState<TResource>(store, stateKey), resource, isLoading: false, initiallyLoaded: true } }))
)
);

const patchableSignal = toDeepPatchableSignal<TResource>(newVal => patchState(store, { [stateKey]: { ...store[stateKey](), resource: newVal } }), store[stateKey].resource);
const patchableSignal = toDeepPatchableSignal<TResource>(newVal => patchState(store, { [stateKey]: { ...getState<TResource>(store, stateKey), resource: newVal } }), (store as Record<string, ResourceStateProps<Signal<TResource>>>)[stateKey].resource);

return {
[connectMehtodName]: (linkRoot: Signal<unknown>, linkName: string) => {
const input = computed(() => ({ resource: linkRoot(), linkName }));
rxConnectToLinkRoot(input);
},
[reloadMethodName]: async (): Promise<void> => {
const currentUrl = store[stateKey].url();
const currentUrl = getState(store, stateKey).url;
if(currentUrl) {
patchState(store, { [stateKey]: { ...store[stateKey](), isLoading: true } });
patchState(store, { [stateKey]: { ...getState<TResource>(store, stateKey), isLoading: true } });

try {
const resource = await requestService.request('GET', currentUrl);
patchState(store, { [stateKey]: { ...store[stateKey](), isLoading: false, resource } });
const resource = await requestService.request<TResource>('GET', currentUrl);
patchState(store, { [stateKey]: { ...getState<TResource>(store, stateKey), isLoading: false, resource } });
} catch(e) {
patchState(store, { [stateKey]: { ...store[stateKey](), isLoading: false, resource: initialValue } });
patchState(store, { [stateKey]: { ...getState<TResource>(store, stateKey), isLoading: false, resource: initialValue } });
throw e;
}
}
Expand Down

0 comments on commit 38613bc

Please sign in to comment.