How can I use type-graphql and RESTDataSource - typegraphql

I wonder how it is possible to use RESTDataSource in type-graphql and thus cache correctly in a redis. I would be grateful for a small example.
At the moment I use the DI container to get a service, which is extended from the RestDataSource class, but this is not the right way.
BookmarkResolver.ts
import { Resolver, FieldResolver, Root, Query, Ctx, Authorized } from 'type-graphql';
import { DealService } from '../service/DealService';
import { AvailableLocale } from '../enum/AvailableLocale';
import { Bookmark } from '../entity/Bookmark';
#Resolver(_of => Bookmark)
export class BookmarkResolver {
constructor(private dealService: DealService) {}
#FieldResolver()
async wordpressDeal(#Root() bookmark: Bookmark) {
return await this.dealService.getDealById(bookmark.item_id, AvailableLocale.STAGING);
}
}
DealService.ts
import { Service } from 'typedi';
import { AbstractService } from './AbstractService';
import { AvailableLocale } from '../enum/AvailableLocale';
#Service()
export class DealService extends AbstractService {
baseURL = process.env.DEAL_SERVICE_URL;
async getDealById(dealId: string | number, locale: AvailableLocale) {
const response = await this.get(
'deals/' + dealId,
{ locale }
);
return this.dealReducer(response);
}
dealReducer(deal: any) {
return {
id: deal.id || 0,
title: deal.title
};
}
}
AbstractService.ts
import { RESTDataSource, HTTPCache } from 'apollo-datasource-rest';
import { Service } from 'typedi';
#Service()
export class AbstractService extends RESTDataSource {
constructor() {
super();
this.httpCache = new HTTPCache();
}
}

Share the RESTDataSource via ApolloServer's context. Use it in the resolver by accessing the context with the #Ctx() decorator.
1. Define a RESTDataSource
Define the data source according to the apollo-datasource-rest example.
export class TodoDataSource extends RESTDataSource {
constructor() {
super();
this.baseURL = "https://jsonplaceholder.typicode.com/todos";
}
async getTodos(): Promise<Todo[]> {
return this.get("/");
}
}
2. Create an instance of the DataSource and put it in the Context
When you start the server, add data sources to the context by defining a function that creates the data sources.
const server = new ApolloServer({
schema,
playground: true,
dataSources: () => ({
todoDataSource: new TodoDataSource(),
}),
});
3. Access the DataSource in the resolver
Use the #Ctx() decorator to access the context in the resolver so you can use the data source.
#Resolver(Todo)
export class TodoResolver {
#Query(() => [Todo])
async todos(#Ctx() context: Context) {
return context.dataSources.todoDataSource.getTodos();
}
}
Full, runnable example at https://github.com/lauriharpf/type-graphql-restdatasource

Related

How to create an Authentication middleware for a Flutter app?

This is my home.dart code and I want to write an Authentication Middleware for my app. At the moment my main.dart code looks like this:
void main() {
Get.put(MenuController());
Get.put(NavigationController());
Get.put(AuthController());
Get.put(AuthCard);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Obx(() => GetMaterialApp(
initialRoute: AuthController.instance.isAuth
? homeScreenRoute
: authenticationScreenRoute,
unknownRoute: GetPage(
name: '/not-found',
page: () => PageNotFound(),
transition: Transition.fadeIn),
getPages: [
GetPage(
name: rootRoute,
page: () {
return SiteLayout();
}),
GetPage(
name: authenticationScreenRoute,
page: () => const AuthenticationScreen()),
GetPage(name: homeScreenRoute, page: () => const HomeScreen()),
],
debugShowCheckedModeBanner: false,
title: 'BasicCode',
theme: ThemeData(
scaffoldBackgroundColor: light,
textTheme: GoogleFonts.mulishTextTheme(Theme.of(context).textTheme)
.apply(bodyColor: Colors.black),
pageTransitionsTheme: const PageTransitionsTheme(builders: {
TargetPlatform.iOS: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
}),
primarySwatch: Colors.blue,
),
));
}
}
And the isAuth variable that I checking at the initialRoute part of the code comes from the following line of codes, inside the auth_controller file that extends GetXController:
final _isAuth = false.obs;
bool get isAuth {
_isAuth.value= token != null;
return _isAuth.value;
}
String? get token {
if (_expiryDate != null &&
_expiryDate!.isAfter(DateTime.now()) &&
_token != null) {
return _token;
}
return null;
}
Everything seems good but the application sticks at the authentication page and won't move to home screen after the isAuth's value changed to true!
I searched for that and found another way by creating an authentication middleware. So I added the following code bellow the above code inside the main.dart file:
class AuthMiddlware extends GetMiddleware {
#override
RouteSettings? redirect(String route) => !AuthController.instance.isAuth
? const RouteSettings(name: authenticationScreenRoute)
: null;
}
But I get a red line under the redirect word with no error decription and don't know how to complete the middleware and make it work?
Example of how to implement an AuthGuard with FirebaseAuth and Getx.
(If not using FirebaseAuth, swap it to your preferred authentication provider in AuthGuardMiddleware.)
middleware.dart
import 'auth.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class AuthGuardMiddleware extends GetMiddleware {
var authService = Get.find<AuthService>();
#override
RouteSettings? redirect(String? route) {
return authService.isLoggedIn() ? null : RouteSettings(name: '/login');
}
}
auth.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:get/get.dart';
class AuthService extends GetxService {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
bool isLoggedIn() {
return _firebaseAuth.currentUser != null;
}
// IMPLEMENT additional FirebaseAuth methods here.
}
main.dart
import 'package:get/get.dart';
import 'middleware.dart';
...
GetPage(
name: '/protected',
page: () => Protected()),
middlewares: [
AuthGuardMiddleware(),
]),
...
Copy paste :)
class AuthMiddlware extends GetMiddleware {
#override
RouteSettings? redirect(String? route) => !AuthController.instance.isAuth
? const RouteSettings(name: authenticationScreenRoute)
: null;
}

How to call a http post method from a service in a parent director

My http method returns results when it is contained in my component, but does not return any results when called from a service located one directory up.
I've checked the console and there are no errors. I have tried printing to the console, which works from within the service (returns the desired data), but does not when run from within the child component.
This is the service that I'm trying to build:
import { Injectable } from '#angular/core';
import { Resturant } from '../../models/resturant.model'
import { HttpClient } from '#angular/common/http';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class GetResturantsService {
fullListresturants: Resturant[];
constructor(private http:HttpClient) { }
fetchList(){
this.http.get('https://lunchlads.firebaseio.com/posts.json')
.pipe(map(responseData =>{
const postsArray: Resturant[] = [];
for (const key in responseData) {
if (responseData.hasOwnProperty(key)){
postsArray.push({ ...responseData[key], id:key })
}
}
return postsArray;
}))
.subscribe(posts => {
// this.fullListresturants = posts;
});
}
}
This is the component which is one file down in the directory:
import { Component, OnInit } from '#angular/core';
import { Resturant } from '../../../models/resturant.model'
import { GetResturantsService } from '../get-resturants.service'
import { HttpClient } from '#angular/common/http';
//import { map } from 'rxjs/operators';
#Component({
selector: 'app-list-all',
templateUrl: './list-all.component.html',
styleUrls: ['./list-all.component.css']
})
export class ListAllComponent implements OnInit {
fullListresturants: Resturant;
constructor(private http:HttpClient, private listAllResturants:GetResturantsService) { }
ngOnInit() {
this.onfullList();
}
onfullList(){
this.fullList();
}
private fullList(){
// this.http.get('https://lunchlads.firebaseio.com/posts.json')
// .pipe(map(responseData =>{
// const postsArray: Resturant[] = [];
// for (const key in responseData) {
// if (responseData.hasOwnProperty(key)){
// postsArray.push({ ...responseData[key], id:key })
// }
// }
// return postsArray;
// }))
// .subscribe(posts => {
// // this.fullListresturants = posts;
// });
this.listAllResturants.fetchList();
}
}
The firebase backend contains roughly 10 records with a name:string, votes:number, and selected:number fields. When run from the component, the html file simply returns the name values with an *ngFor loop.
When run from the service, nothing is returned and no errors are reported in the console.
I suspect the problem lies somewhere in how I am calling the fetchList method from the component, but google and me have not been able to suss out what I'm doing wrong.
Your service should return an observable to make it work. As per your current code, you are not returning anything from GetResturantsService.fetchList(). To make it work let change the service like this:
export class GetResturantsService {
fullListresturants: Resturant[];
constructor(private http:HttpClient) { }
fetchList(){
return this.http.get('https://lunchlads.firebaseio.com/posts.json')
.pipe(map(responseData =>{
const postsArray: Resturant[] = [];
for (const key in responseData) {
if (responseData.hasOwnProperty(key)){
postsArray.push({ ...responseData[key], id:key })
}
}
return postsArray;
}));
}
}
Now in component subscribe to the observable returned from fetchList method like this:
export class ListAllComponent implements OnInit {
fullListresturants: Resturant;
constructor(private http:HttpClient, private listAllResturants:GetResturantsService) { }
ngOnInit() {
this.onfullList();
}
onfullList(){
this.fullList();
}
private fullList(){
this.listAllResturants.fetchList()
.subscribe(posts => {
//DO whatever you want to do with posts
this.fullListresturants = posts;
});
}
}
Hope it helps.

Dependencies not being injected on NestJS with Serverless and AWS

Everyone,
I'm trying to setup my first NestJS application. It is backed by Serverless on AWS.
I created a simple Controller that has a Service as a dependency. When I hit the endpoint with my HTTP Client, the object that should contain the Service instance is undefined. I'm not able to make it work. Could you help?
handler.ts
import { Context, Handler } from 'aws-lambda';
import { NestFactory } from '#nestjs/core';
import { AppModule } from './src/module';
import { Server } from 'http';
import { ExpressAdapter } from '#nestjs/platform-express';
import * as serverless from 'aws-serverless-express';
import * as express from 'express';
import {DB} from './src/libs/db';
let cachedServer: Server;
function bootstrapServer(): Promise<Server> {
const expressApp = express();
const adapter = new ExpressAdapter(expressApp);
return NestFactory.create(AppModule, adapter)
.then(app => app.enableCors())
.then(app => app.init())
.then(() => DB.connect())
.then(() => serverless.createServer(expressApp));
}
export const handle: Handler = (event: any, context: Context) => {
if (!cachedServer) {
bootstrapServer().then(server => {
cachedServer = server;
return serverless.proxy(server, event, context);
});
} else {
return serverless.proxy(cachedServer, event, context);
}
};
module.ts
import { Module } from '#nestjs/common';
import { EventController } from './event.controller';
import { EventService } from './event.service';
#Module({
controllers: [EventController],
providers: [EventService],
})
export class AppModule {}
event.service.ts
import { Injectable } from '#nestjs/common';
interface Event{}
#Injectable()
export class EventService {
create(event: Event) {
return {
id: Date.now()
}
}
}
event.controller.ts
import { Controller, Post, Body } from '#nestjs/common';
import { EventService } from './event.service';
interface Event { }
#Controller('event')
export class EventController {
constructor(private readonly eventService: EventService) { }
#Post()
async create(#Body() req)
{
this.eventService.create(req);
}
}
So this.eventService is always undefined. What is wrong with this implementation?
Maybe you are missing a line from tsconfig add this below:
"emitDecoratorMetadata": true
Credits to the God of Nestjs Mr. Kamil's reply:
https://stackoverflow.com/a/50121886/6301493

Pass data to not-yet-loaded view in Aurelia

I am navigating from one view (call it bestSellersView) to another (BookDetailsView). There are multiple different 'parent' views that can navigate to 'Book Details' and they all need to pass the book that is to be viewed to the next view. I don't want to inject the source view to the details view as some threads suggest since my constructor would grow with each new view that uses the details sub-view.
I am trying to use the event aggregator, however due to the life cycle of things I am always getting a blank details screen on the first time I navigate. When I first navigate to the 'book details' view the ViewDetailsMessage has not yet been subscribed to before the publisher (best sellers) sends the message. Since I have my viewmodel set to singleton, the subsequent clicks work fine (since the details view is already constructed and subscribed to the event).
How can I get around this chicken-egg problem in Aurelia?
Edit 01
Here is what I was doing when I was having a problem:
Master.ts:
import { JsonServiceClient } from "servicestack-client";
import {
ListPendingHoldingsFiles,
ListPendingHoldingsFilesResponse,
SendHoldings,
PositionFileInfo
} from "../holdingsManager.dtos";
import { inject, singleton } from "aurelia-framework";
import { Router } from "aurelia-router";
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";
#singleton()
#inject(Router, EventAggregator)
export class Pending {
router: Router;
positions: PositionFileInfo[];
client: JsonServiceClient;
eventAgg: EventAggregator;
constructor(router, eventAggregator) {
this.router = router;
this.eventAgg = eventAggregator;
this.client = new JsonServiceClient('/');
var req = new ListPendingHoldingsFiles();
this.client.get(req).then((getHoldingsResponse) => {
this.positions = getHoldingsResponse.PositionFiles;
}).catch(e => {
console.log(e); // "oh, no!"
});
}
openHoldings(positionInfo) {
this.eventAgg.publish(new GetPendingPositionMessage(positionInfo));
this.router.navigate('#/holdings');
}
}
Child.ts:
import { JsonServiceClient } from "servicestack-client";
import { inject, singleton } from "aurelia-framework";
import { Router } from 'aurelia-router';
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";
import {
GetPendingHoldingsFile,
GetPendingHoldingsFileResponse,
Position,
PositionFileInfo
} from "../holdingsManager.dtos";
#singleton()
#inject(Router, EventAggregator)
export class Holdings {
router: Router;
pendingPositionFileInfo: PositionFileInfo;
position: Position;
client: JsonServiceClient;
eventAgg: EventAggregator;
constructor(router, eventAggregator) {
this.router = router;
this.eventAgg = eventAggregator;
this.eventAgg.subscribe(GetPendingPositionMessage,
message => {
this.pendingPositionFileInfo = message.fileInfo;
});
}
activate(params, routeData) {
this.client = new JsonServiceClient('/');
var req = new GetPendingHoldingsFile();
req.PositionToRetrieve = this.pendingPositionFileInfo;
this.client.get(req).then((getHoldingsResponse) => {
this.position = getHoldingsResponse.PendingPosition;
}).catch(e => {
console.log(e); // "oh, no!"
});
}
}
Here is what I am doing now:
master.ts
import { JsonServiceClient } from "servicestack-client";
import {
ListPendingHoldingsFiles,
ListPendingHoldingsFilesResponse,
PositionFileInfo
} from "../holdingsManager.dtos";
import { inject, singleton } from "aurelia-framework";
import { Router } from "aurelia-router";
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";
import { SetPendingPositionMessage } from "../common/SetPendingPositionMessage";
#singleton()
#inject(Router, EventAggregator)
export class Pending {
router: Router;
eventAgg: EventAggregator;
positions: PositionFileInfo[];
client: JsonServiceClient;
fileInfo: PositionFileInfo;
constructor(router, eventAggregator) {
this.router = router;
this.eventAgg = eventAggregator;
this.eventAgg.subscribe(GetPendingPositionMessage, () => {
this.eventAgg.publish(new SetPendingPositionMessage(this.fileInfo));
});
}
activate(params, routeData) {
this.client = new JsonServiceClient('/');
var req = new ListPendingHoldingsFiles();
this.client.post(req).then((getHoldingsResponse) => {
this.positions = getHoldingsResponse.PositionFiles;
}).catch(e => {
console.log(e); // "oh, no!"
});
}
openHoldings(positionInfo) {
this.fileInfo = positionInfo;
this.router.navigate('#/holdings');
}
}
child.ts
import { JsonServiceClient } from "servicestack-client";
import { inject, singleton } from "aurelia-framework";
import { Router } from 'aurelia-router';
import {
GetPendingHoldingsFile,
GetPendingHoldingsFileResponse,
Position,
SendHoldings,
PositionFileInfo
} from "../holdingsManager.dtos";
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";
import { SetPendingPositionMessage } from "../common/SetPendingPositionMessage";
import { GetDeliveredPositionMessage } from "../common/GetDeliveredPositionMessage";
import { SetDeliveredPositionMessage } from "../common/SetDeliveredPositionMessage";
#singleton()
#inject(Router, EventAggregator)
export class Holdings {
router: Router;
pendingPositionFileInfo: PositionFileInfo;
position: Position;
client: JsonServiceClient;
eventAgg: EventAggregator;
constructor(router, eventAggregator) {
this.router = router;
this.eventAgg = eventAggregator;
this.eventAgg.subscribe(SetPendingPositionMessage, message => this.getPositionData(message.fileInfo));
this.eventAgg.subscribe(SetDeliveredPositionMessage, message => this.getPositionData(message.fileInfo));
}
getPositionData(fileInfo) {
this.position = null;
this.client = new JsonServiceClient('/');
var req = new GetPendingHoldingsFile();
req.PositionToRetrieve = fileInfo;
this.client.post(req).then((getHoldingsResponse) => {
this.position = getHoldingsResponse.PendingPosition;
}).catch(e => {
console.log(e); // "oh, no!"
});
}
activate(params) {
this.eventAgg.publish(new GetPendingPositionMessage());
this.eventAgg.publish(new GetDeliveredPositionMessage());
}
sendHoldings() {
var req = new SendHoldings();
this.client.get(req).then((sendHoldingsRepsonse) => {
console.log("SUCCESS!"); // "oh, no!"
}).catch(e => {
console.log(e); // "oh, no!"
});
}
}
I need to add a bit of logic to the activate method of the child to ensure I ask for the right parents holdings file.
Sounds like you need to share state between views. I use a StateStore class that is injected into any views that wish to share state. By default all objects injected are Singletons making it easy to share state. A very simple example could be (in TypeScript):
statestore.ts
export class StateStore {
state: any;
}
masterview.ts
autoinject()
export class MasterView {
constructor(private store: StateStore){
}
doSomething(): void {
this.store.state = "some value";
// navigate to detail view
}
}
detailview.ts
autoinject()
export class DetailView {
sharedValue: any;
constructor(store: StateStore) {
this.sharedValue = store.state;
}
}
This will share a single instance of StateStore between views allowing state to easily be shared.
My current solution, though not as pretty as I'd like it to be is as follows:
Source view (bestSellersView) is a singleton and subscribes to "GetCurrentBookMessage". When a user selects a book, the Source saves it locally and navigates to the "BookDetailsView". The BookDetailsView is constructed, subscribes to a "SetCurrentBookMessage" and, when activated, it sends a GetCurrentBookMessage. The source view answers with a "SetCurrentBookMessage".
This will get messy with multiple sources and I will have to have some way to resolve where the navigation came from to pick the 'right' source, but for today this works.
Edit 01
I have also tried getting rid of all the event aggregator stuff and putting this in the master's OpenHoldings method:
let routeConfig = this.router.routes.find(x => x.name === 'holdings');
this.fileInfo = positionInfo;
routeConfig.settings = {
fileInfo: positionInfo
};
this.router.navigateToRoute('holdings');
And then putting this in the child's activate method:
activate(urlParams, routeMap, navInstr) {
this.getPositionData(routeMap.settings.fileInfo);
}
But the settings did not persist after the navigation was executed.

Angular2 Service Unit Test Fails with TypeError: undefined is not an object

I am testing my authService. This is the full Test, yet Karma tells me, authService is undefined. I have plenty of Service which AuthService depends upon, but I provided and injected them all properly.
Error: Cannot resolve all parameters for 'AuthService'(BackendService, Store, LoggerService, undefined, ErrorService). Make sure that all the parameters are decorated with Inject or have valid type annotations and that 'AuthService' is decorated with Injectable. in /var/folders/zb/tpysrhsx7hbg1dnsn4gwtqq00000gn/T/8715f9a6c29e748f52c8f59e3e1daae3.browserify (line 34976)
authservice.spec.ts
import { provide } from "#angular/core";
import { AuthHttp } from "angular2-jwt";
import { HTTP_PROVIDERS, XHRBackend } from "#angular/http";
import { MockBackend } from "#angular/http/testing";
import {
TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS
} from "#angular/platform-browser-dynamic/testing";
import {
beforeEachProviders,
inject,
beforeEach,
it,
describe,
setBaseTestProviders
} from "#angular/core/testing";
import { Subject } from "rxjs/Subject";
import { AuthService } from "./auth.service";
import { BackendService } from "../../backend/backend.service";
import { ErrorService } from "../../error/error.service";
import { LoggerService } from "../../logger/logger.service";
import { NavService } from "../../nav/nav-service/nav.service";
import { Store } from "#ngrx/store";
import { TestComponentBuilder } from "#angular/compiler/testing";
import { ToastController, AlertController } from "ionic-angular";
setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
describe("AuthService", () => {
let response = new Subject();
let tcb;
let authService;
let navService;
let backendService;
let errorService;
let store;
let loggerService;
class StubErrorService extends ErrorService {
constructor() {
super(null, null);
}
toast(title) {
console.error(title);
}
modal(title, subtitle) {
console.error(title, subtitle);
}
}
class StubBackendService extends BackendService {
}
class StubStore extends Store<any> {
}
class StubLoggerService extends LoggerService {
}
class StubNavService extends NavService {
}
// PROVIDE
beforeEachProviders(() => [
HTTP_PROVIDERS,
provide(AuthHttp, {
useValue: {
get: (url: string) => {
return response;
}
}
}),
AuthService,
TestComponentBuilder,
provide(ToastController, {useClass: null}),
provide(AlertController, {useClass: null}),
provide(ErrorService, {useClass: StubErrorService}),
provide(XHRBackend, {useClass: MockBackend}),
provide(BackendService, {useClass: StubBackendService}),
provide(Store, {useClass: StubStore}),
provide(LoggerService, {useClass: StubLoggerService}),
provide(NavService, {useClass: StubNavService})
]);
// INJECTS
beforeEach(inject([TestComponentBuilder, AuthService, ErrorService, BackendService, Store, LoggerService, NavService], (_tcb, as, es, bs, s, ls, ns) => {
tcb = _tcb;
authService = as;
navService = ns;
errorService = es;
store = s;
backendService = bs;
loggerService = ls;
}));
it("should test authservice", () => {
authService.logout();
});
});
I don't know if its relevant anymore, just to say I had an almost identical issue and I resolved it following the official docs on how to test services. Hope it helps!