Access to files in MongoDB GridFS with NestJS and GraphQL - mongodb

I'm working with NestJS with GraphQL and MongoDB.
I'm trying store image files using GridFS using mongo-gridfs package.
Uploading images to database works fine, but how can I access to this files?
I mean for example I want to get source path of this files and use it in my frontend
Here is my resolver and service:
// photo.resolver.ts
import { Resolver, Mutation, Query, Args } from '#nestjs/graphql';
import { GraphQLUpload, FileUpload } from 'graphql-upload';
import { Photo } from './dto/photo.dto';
import { PhotoService } from './services/photo.service';
#Resolver()
export class PhotoResolver {
constructor(
private readonly photoService: PhotoService
) {}
#Query(() => ???, { nullable: true })
async photo(#Args('id', { nullable: true }) id: string) {
const photo = await this.photoService.findOne(id);
return ???;
}
#Mutation(() => Photo)
async uploadPhoto(#Args({name: 'file', type: () => GraphQLUpload}) file: FileUpload) {
return await this.photoService.save(file);
}
}
// photo.service.ts
import { Connection } from 'mongoose';
import { Injectable } from '#nestjs/common';
import { InjectConnection } from '#nestjs/mongoose';
import { FileUpload } from 'graphql-upload';
import { MongoGridFS } from 'mongo-gridfs';
import { Photo } from '../photo.interface';
#Injectable()
export class PhotoService {
private fileModel: MongoGridFS;
constructor(
#InjectConnection() private readonly connection: Connection
) {
this.fileModel = new MongoGridFS(this.connection.db as any, 'photo');
}
async findOne(id: string) {
return await this.fileModel.findById(id);
}
async save(file: FileUpload): Promise<Photo> {
return await this.fileModel.writeFileStream(file.createReadStream(), {
filename: file.filename,
contentType: file.mimetype
});
}
}
I've tried two approached:
I used downloadFile method from my photoModel, but it returns path to this file in my Temp directory in local disk.
// photo.service.ts
async findOne(id: string): Promise<string> {
return await this.fileModel.downloadFile(id); // C:\...\AppData\Local\Temp\189450ef
}
// photo.resolver.ts
#Query(() => String, { nullable: true })
async photo(#Args('id', { nullable: true }) id: string) {
return id && await this.photoService.findOne(id);
}
It works per se but it doesn't look to me as a proper solution. I'd prefer that source path should "lead" to my server.
I used readFileStream method from my photoModel, which return filestream and added #Res() res to arguments in resolver.
// photo.service.ts
async findOne(id: string): Promise<GridFSBucketReadStream> {
return await this.fileModel.readFileStream(id);
}
// photo.resolver.ts
#Query(() => Boolean)
async photo(#Args('id', { nullable: true }) id: string, #Res() res) {
const photoStream = await this.photoService.findOne(id);
photoStream.pipe(res);
return true;
}
And now I've got such an error in terminal:
[Nest] 12408 - 10.07.2021, 13:02:25 [ExceptionsHandler] dest.on is not a function +27555ms
TypeError: dest.on is not a function

Related

ConnectorError Prisma with MongoDB

I am developing an API with Nestjs and MongoDB. Well sometimes I have this problem. It doesn't always happen and that's what I have to solve because this error rarely happens.
This is the error:
{
"status": "error",
"message": "\nInvalid this.prisma.people.findMany() invocation in\nC:\ima\empresas\datec\api-official\src\modules\Events\services\utils.service.ts:57: 28\n\n 54 }),\n 55 \n 56 // Get attendees by id\n→ 57 this.prisma.people.findMany(\nError in batch request 3: Error occurred during query execution:\nConnectorError(ConnectorError { user_facing_error: None, kind: RawDatabaseError { code: "unknown", message: "An existing connection was forced to be terminated by the remote host. (os error 10054)" } })"
}
I really can't find much information to solve this error. I have followed the steps in the documentation on Nestjs when using prisma with nestjs. I need help to solve this error, since it is essential to find a solution as soon as possible. Thanks so much for read this question
UPDATE
The Prisma service
// Librarys
import { Injectable } from '#nestjs/common'
import { PrismaClient } from '#prisma/client'
// Interfaces
import { INestApplication, OnModuleInit, OnModuleDestroy } from '#nestjs/common'
#Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
async onModuleInit() {
await this.$connect()
}
async onModuleDestroy() {
await this.$disconnect()
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close()
})
}
}
File that starts the application
//Libraries
import { NestFactory } from '#nestjs/core'
import { ValidationPipe } from '#nestjs/common'
import { SwaggerModule, DocumentBuilder } from '#nestjs/swagger'
import { useContainer } from 'class-validator'
// Modules
import { MainModule } from './main.module'
// config
import pk from '#root/package.json'
import { corstOptions } from '#config/cors'
import { PORT, APP_NAME, PUBLIC_URL } from '#config/env'
// Exceptions
import { HttpExceptionFilter } from '#utils/HttpExceptionFilter'
import { PrismaService } from '#root/src/services/prisma'
async function bootstrap() {
// Create application
const app = await NestFactory.create(MainModule)
// Enable cors
app.enableCors(corstOptions)
// Use global 'api' prefix, all calls will come after '/api/*'
app.setGlobalPrefix('api')
// Globally define custom response
app.useGlobalFilters(new HttpExceptionFilter())
// Enable prism on custom validations
useContainer(app.select(MainModule), { fallbackOnErrors: true })
// Get service from primsa and enable 'shutdowns'
const prismaService = app.get(PrismaService)
await prismaService.enableShutdownHooks(app)
// Use 'pipe' validation to validate the 'body' structure
app.useGlobalPipes(
newValidationPipe({
whitelist: true,
transform: true,
forbidUnknownValues: true,
forbidNonWhitelisted: true,
transformOptions: { enableImplicitConversion: true }
})
)
// Create API documentation
const config = new DocumentBuilder()
.addBearerAuth()
.setTitle(`API | ${APP_NAME}`)
.setContact(pk.author.name, pk.author.url, pk.author.email)
.setDescription(pk.description)
.setVersion(pk.version)
.build()
const document = SwaggerModule.createDocument(app, config)
SwaggerModule.setup('/', app, document)
// Listen to the application on the PORT defined in the environment variables
await app.listen(PORT || 0, () => {
// eslint-disable-next-line no-console
console.log(
'\x1b[33m%s\x1b[0m',
`[INFO] The server has been started at '${PUBLIC_URL}'`
)
})
}
bootstrap() // Run application
utils.service of Event module
// Librarys
import { Prisma } from '#prisma/client'
import { Inject, Injectable } from '#nestjs/common'
// Services
import { PrismaService } from '#services/prisma'
// DTO
import { EventBodyDTO } from '#modules/Events/events.dto'
// Arguments
import { EventsArgs } from '#modules/Events/events.args'
#Injectable()
export class UtilsService {
#Inject(PrismaService)
private readonly prisma: PrismaService
private readonly selects = {
'id.title': { id: true, title: true },
'id.fullname': { id: true, fullname: true },
'id.title.attributes': { id: true, title: true, attributes: true },
'id.code.description.attributes': {
id: true,
code: true,
description: true,
attributes: true
}
}
/**
* Get reminders, types of activities, case, client and attendees of an event
* #param {EventBodyDTO} payload Event data
*/
async getEventFields(payload: EventBodyDTO) {
const [activityType, eventCase, client, assistants, reminders] =
await Promise.all([
// Get an activity type by id
this.prisma.parameters.findUnique({
select: this.selects['id.title'],
where: { id: payload.activityType }
}),
// Get a case by id
this.prisma.expedients.findUnique({
where: { id: payload.case },
select: this.selects['id.code.description.attributes']
}),
// Get a person by id
this.prisma.people.findFirst({
select: this.selects['id.fullname'],
where: { isClient: true, id: payload.client }
}),
// Get attendees by id
this.prisma.people.findMany({
select: this.selects['id.fullname'],
where: {
isEmployee: true,
id: { in: payload.assistants }
}
}),
// Get reminders by id
this.prisma.parameters.findMany({
select: this.selects['id.title.attributes'],
where: {
id: { in: payload.reminders }
}
})
])
return {
reminders: reminders,
assistants: assistants,
client: client === null ? {} : client,
case: eventCase === null ? {} : eventCase,
activityType: activityType === null ? {} : activityType
}
/**
* Create filters to filter by customer or event attendees
* #param {EventsArgs} args Arguments to filter
* #returns {Promise<Prisma.EventsFindManyArgs['where']>} Returns an object with filters
*/
async createEventFilters(
args: EventsArgs
): Promise<Prisma.EventsFindManyArgs['where']> {
const filters: Prism.EventsFindManyArgs['where'] = {
userId: args.user
}
// Filter by customer
if (typeof args.client === 'string') {
const clients = await this.prisma.people.findMany({
where: {
isClient: true,
fullname: {
mode: 'insensitive',
contains: args.client
}
},
select: {
id: true,
fullname: true
}
})
filters.OR = []
for (const client of clients) {
filters.OR.push({
client: { equals: client }
})
}
}
// Filter by attendees
if (Array.isArray(args.assistants)) {
const assistants = await this.prisma.people.findMany({
where: {
isEmployee: true,
id: {
in: args.assistants
}
},
select: {
id: true,
fullname: true
}
})
if (!Array.isArray(filters.OR)) {
filters.OR = []
}
filters.OR.push({
assistants: { hasSome: assistants }
})
}
return filters
}
}

Mongoose Schema properties validation with Typescript NextJS

i am trying to save new document to mongo db, the Schema validation is not working for me, i am trying ti make required true, but i still can add new document without the required field.
this is my schema:
// lib/models/test.model.ts
import { Model, Schema } from 'mongoose';
import createModel from '../createModel';
interface ITest {
first_name: string;
last_name: string;
}
type TestModel = Model<ITest, {}>;
const testSchema = new Schema<ITest, TestModel>({
first_name: {
type: String,
required: [true, 'Required first name'],
},
last_name: {
type: String,
required: true,
},
});
const Test = createModel<ITest, TestModel>('tests', testSchema);
module.exports = Test;
this is createModel:
// lib/createModel.ts
import { Model, model, Schema } from 'mongoose';
// Simple Generic Function for reusability
// Feel free to modify however you like
export default function createModel<T, TModel = Model<T>>(
modelName: string,
schema: Schema<T>
): TModel {
let createdModel: TModel;
if (process.env.NODE_ENV === 'development') {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
// #ts-ignore
if (!global[modelName]) {
createdModel = model<T, TModel>(modelName, schema);
// #ts-ignore
global[modelName] = createdModel;
}
// #ts-ignore
createdModel = global[modelName];
} else {
// In production mode, it's best to not use a global variable.
createdModel = model<T, TModel>(modelName, schema);
}
return createdModel;
}
and this is my tests file:
import { connection } from 'mongoose';
import type { NextApiRequest, NextApiResponse } from 'next';
const Test = require('../../../lib/models/test.model');
import { connect } from '../../../lib/dbConnect';
const ObjectId = require('mongodb').ObjectId;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
switch (req.method) {
case 'POST': {
return addPost(req, res);
}
}
}
async function addPost(req: NextApiRequest, res: NextApiResponse) {
try {
connect();
// const { first_name, last_name } = req.body;
const test = new Test({
first_name: req.body.first_name,
last_name: req.body.last_name,
});
let post = await test.save();
// return the posts
return res.json({
message: JSON.parse(JSON.stringify(post)),
success: true,
});
// Erase test data after use
//connection.db.dropCollection(testModel.collection.collectionName);
} catch (err) {
//res.status(400).json(err);
res.status(400).json({
message: err,
success: false,
});
}
}
in the Postman, i send a request body without the required field (first_name) and i still can add it.
any help?

How to set run time variable to postgresql in typeorm and nest js

Iam using the row level security in supabase with nest.js, So how can I set runtime variables safely to the DB so that I can be sure that the variables sync with each app user (due to the http request triggered the execution)?
I saw that it is possible to set local variables in a transaction but I wouldn't like to wrap all the queries with transactions.
Thanks & Regards
I tried to execute this with subscribers in nestjs it working fine . but it wont have a function like beforeSelect or beforeLoad , so i drop it
import { Inject, Injectable, Scope } from '#nestjs/common';
import { InjectDataSource } from '#nestjs/typeorm';
import { ContextService } from 'src/context/context.service';
import { DataSource, EntityManager, LoadEvent, RecoverEvent, TransactionRollbackEvent, TransactionStartEvent } from 'typeorm';
import {
EventSubscriber,
EntitySubscriberInterface,
InsertEvent,
UpdateEvent,
RemoveEvent,
} from 'typeorm';
#Injectable()
#EventSubscriber()
export class CurrentUserSubscriber implements EntitySubscriberInterface {
constructor(
#InjectDataSource() dataSource: DataSource,
private context: ContextService,
) {
dataSource.subscribers.push(this);
}
async setUserId(mng: EntityManager, userId: string) {
await mng.query(
`SELECT set_config('request.jwt.claim.sub', '${userId}', true);`,
);
}
async beforeInsert(event: InsertEvent<any>) {
try {
const userId = this.context.getRequest();
await this.setUserId(event.manager, userId);
} catch (err) {
console.log(err);
}
}
async beforeTransactionRollback(event: TransactionRollbackEvent) {
console.log('hello')
try {
const userId = this.context.getRequest();
await this.setUserId(event.manager, userId);
} catch (err) {
console.log(err);
}
}
async beforeUpdate(event: UpdateEvent<any>) {
try {
const userId = this.context.getRequest();
await this.setUserId(event.manager, userId);
} catch (err) {
console.log(err);
}
}
async beforeRemove(event: RemoveEvent<any>) {
try {
const userId = this.context.getRequest();
await this.setUserId(event.manager, userId);
} catch (err) {
console.log(err);
}
}
}
After i get to know that we can use query runner instead of subscriber . but its not working ,
also i need a common method to use all the queries
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Users } from 'src/common/entities';
import { DataSource, EntityManager, Repository } from 'typeorm';
#Injectable()
export class UsersService {
constructor(
#InjectRepository(Users) private userRepository: Repository<Users>,
private dataSource: DataSource,
private em: EntityManager,
) {}
getAllUsers(userId: string) {
const queryRunner = this.dataSource.createQueryRunner();
return new Promise(async (resolve, reject) => {
let res: any;
try {
await queryRunner.connect();
await queryRunner.manager.query(
// like this we can set the variable
`SELECT set_config('request.jwt.claim.sub', '${userId}', true);`,
);
// after setting config variable the query should return only one user by userId
res = await queryRunner.query('SELECT * FROM users');
// but it reurns every user
} catch (err) {
reject(err);
} finally {
await queryRunner.manager.query(`RESET request.jwt.claim.sub`);
await queryRunner.release();
resolve(res);
}
});
}
}
Thanks in advance....
Sorry to say, bro. But in currently state of development TypeORM does not have a feature that let us set conection variables. The roundabout for your problem is to do something like this.
/**
* Note: Set current_tenant session var and executes a query on repository.
* Usage:
* const itens = = await tenantTransactionWrapper( manager => {
* return manager.getRepository(Entity).find();
* });
*
* #param {function} callback - a function thar receives an Entity Manager and returns a method to be executed by tenantTransactionWrapper
* #param {string} providedTenantId - optional tenantId, otherwise tenant will be taken from localStorage
*/
async function tenantWrapper<R>(
callback: (manager: EntityManager) => Promise<R>,
providedTenantId?: string,
) {
const tenantId = providedTenantId || tenantStorage.get();
let response: R;
await AppDataSource.transaction(async manager => {
await manager.query(`SET LOCAL smsystem.current_tenant='${tenantId}';`);
response = await callback(manager);
});
return response;
}
Then create a custom repository to make use of the wraper a little bit simple.
const customRepository = <T>(entity: EntityTarget<T>) => ({
find: (options?: FindManyOptions<T>) =>
tenantTransactionWrapper(mng => mng.getRepository(entity).find(options))(),
findAndCount: (options?: FindManyOptions<T>) =>
tenantTransactionWrapper(mng =>
mng.getRepository(entity).findAndCount(options),
)(),
save: (entities: DeepPartial<T>[], options?: SaveOptions) =>
tenantTransactionWrapper(mng =>
mng.getRepository(entity).save(entities, options),
)(),
findOne: (options: FindOneOptions<T>) =>
tenantTransactionWrapper(async mng =>
mng.getRepository(entity).findOne(options),
)(),
remove: (entities: T[], options?: RemoveOptions) =>
tenantTransactionWrapper(mng =>
mng.getRepository(entity).remove(entities, options),
)(),
createQueryBuilder: () => {
throw new Error(
'Cannot create queryBuilder for that repository type, instead use: tenantWrapper',
);
},
tenantTransactionWrapper,
});
And finally use our customRepository :
class PersonsRepository implements IPersonsRepository {
private ormRepository: Repository<Person>;
constructor() {
this.ormRepository = AppDataSource.getRepository<Person>(Person).extend(
customRepository(Person),
);
}
public async create(data: ICreatePersonDTO): Promise<Person> {
const newPerson = this.ormRepository.create(data);
await this.ormRepository.save(newPerson);
return newPerson;
}
public async getAll(relations: string[] = []): Promise<Person[]> {
return this.ormRepository.find({ relations });
}
I hope this may help someone and will be very glad if someone provides a better solution.
First you have to create a custom class for wrapping your userId or any stuff
custome_service.ts ==>
#Injectable()
export class UserIdWrapper {
constructor(private dataSource: DataSource) {}
userIdWrapper = (callback: (mng: QueryRunner) => Promise<any>, userId: string) => {
const queryRunner = this.dataSource.createQueryRunner();
return new Promise(async (resolve, reject) => {
let res: any;
try {
await queryRunner.connect();
await queryRunner.manager.query(
`SELECT set_config('your_variable_name', '${userId}', false)`,
);
//here is your funciton your calling in the service
res = await callback(queryRunner);
} catch (err) {
reject(err);
} finally {
await queryRunner.manager.query(`RESET your_variable_name`);
await queryRunner.release();
resolve(res);
}
});
};
}
Now here you have to call the function inside user service
user.service.ts ==>
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Users } from 'src/common/entities';
import { UserIdWrapper } from 'src/common/local-settup/userId_wrapper';
import { DataSource, EntityManager, QueryRunner, Repository } from 'typeorm';
#Injectable()
export class UsersService {
constructor(
#InjectRepository(Users) private userRepository: Repository<Users>,
private dataSource: DataSource,
private userIdWrapper: UserIdWrapper
) {}
async getAllUsers(userId: string) {
//This is your call back funciton that have to pass
const findOne = async (queryRunner: QueryRunner) => {
const res = await queryRunner.query('SELECT * FROM public.users');
return res;
};
try {
//hear we are passing the function in to the class funciton
return this.userIdWrapper.userIdWrapper(findOne, userId);
} catch (err) {
console.log(err);
}
}
}
Dont forgot to provide the custom class service inside the provider of user service.

Cannot read property forEach of undefined

The title of this question is just the error I am currently receiving, but what I really need help with is understanding observables and API calls. For whatever reason, I just haven't been able to get a good grasp of this concept, and I am hoping that someone might have an explanation that will finally click.
I am trying to create a new Angular service that retrieves JSON from an API. I then need to map the response to a model. Due to weird naming conventions, job descriptions and job requirements are used interchangeably here. Here is my service class.
import { CommunicationService } from './communication.service';
import { AiDescription } from '../models/ai-description.model';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
#Injectable()
export class AiDescriptionService {
requirements: Observable<AiDescription[]>;
private aiDescriptionUrl: string = '/api/core/company/jobdescriptions';
private dataStore: {
requirements: AiDescription[]
};
private _requirements: BehaviorSubject<AiDescription[]>;
private emptyRequestParams = {
"company_id": "",
"carotene_id": "",
"carotene_version": "",
"city": "",
"state": "",
"country": ""
};
readonly caroteneVersion: string = "caroteneV3";
constructor(
private communicationService: CommunicationService
) {
this.dataStore = { requirements: [] };
this._requirements = new BehaviorSubject<AiDescription[]>([]);
this.requirements = this._requirements.asObservable();
}
LoadRequirements(params: Object) {
this.communicationService.postData(this.aiDescriptionUrl, params)
.subscribe(res => {
let jobDescriptions = [];
jobDescriptions = res.jobdescriptions;
jobDescriptions.forEach((desc: { id: string; description: string; }) => {
let aiDescription = new AiDescription();
aiDescription.id = desc.id;
aiDescription.description = desc.description;
});
this.dataStore.requirements = res;
this._requirements.next(Object.assign({}, this.dataStore).requirements);
});
}
CreateRequest(
companyID : string,
caroteneID : string,
city: string,
state: string,
country: string
): Object {
let newRequestParams = this.emptyRequestParams;
newRequestParams.company_id = companyID;
newRequestParams.carotene_id = caroteneID;
newRequestParams.carotene_version = this.caroteneVersion;
newRequestParams.city = city;
newRequestParams.state = state;
newRequestParams.country = country;
this.LoadRequirements(newRequestParams);
return this.dataStore;
}
}
The postData() function being called by this.communicationService is here:
postData(url: string, jobInformation: any): Observable<any> {
const start = new Date();
const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
const body = JSON.stringify(jobInformation);
const options = { headers };
return this.http.post(url, body, options)
.catch(err => Observable.throw(err))
.do(() => {
this.analyticsLoggingService.TrackTiming('JobPostingService', 'PostSuccess', new Date().getTime() - start.getTime());
}, () => {
this.analyticsLoggingService.TrackError('JobPostingService', 'PostFailure');
});
}
I didn't write the postData function, and I would not be able to modify it. When running a unit test, I am getting this error: "TypeError: Cannot read property 'forEach' of undefined".
But more than simply fixing the error, I am really trying to get a better understanding of using Observables, which is something I haven't been able to get a good understanding of from other sources.
In your example, I recommend replacing any and Object with explicitly defined models.
Here's an example for Angular 8 for Subscription, Promise, and Observable API calls. You can get more info here: https://angular.io/tutorial/toh-pt6.
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '#angular/common/http';
import { Observable } from 'rxjs';
import { User } from './user.model';
#Injectable({ providedIn: 'root' })
export class UserService {
users: User[];
authHeaders = new HttpHeaders()
.set('Content-Type', 'application/json');
constructor(
private readonly http: HttpClient
) { }
getUsers() {
this.http.get(`https://myApi/users`, { headers: this.authHeaders })
.subscribe(
(data: User[]) => {
this.users = data;
}, (error: HttpErrorResponse) => { /* handle error */ });
}
async getUserPromise(userID: number): Promise<User> {
const url = `https://myApi/users/${userID}`;
return this.http.get<User>(url, { headers: this.authHeaders })
.toPromise();
}
getUserObservable(userID: number): Observable<User> {
const url = `https://myApi/users/${userID}`;
return this.http.get<User>(url, { headers: this.authHeaders });
}
}
I like to keep my class models in separate files. This example would have user.model.ts with content like:
export class User {
constructor(
public id: number,
public username: string,
public displayName: string,
public email: string
) { }
}
I've not included authentication headers or error handling for brevity; however, you might want to add those as needed.

Angular 2 data service

I'm building an observable data service based on the following article: https://coryrylan.com/blog/angular-2-observable-data-services
In the article he used an array as an example, here I will use the user object since I'm developing the user service.
Here's what I got:
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Events, SqlStorage, Storage } from 'ionic-angular';
import { Subject } from 'rxjs/Subject';
export interface DataStore {
user: Object
}
#Injectable()
export class UserService {
private baseUrl: string;
private storage: Storage;
private _user$: Subject<Object>;
private dataStore: DataStore;
constructor(
private http: Http
) {
this.baseUrl = 'http://localhost:3000';
this.storage = new Storage(SqlStorage);
this._user$ = <Subject<Object>>new Subject();
this.dataStore = {
user: { name: '' }
};
}
set user$(user: Object) {
this.storage.set('user', JSON.stringify(user));
this.dataStore.user = user;
this._user$.next(this.dataStore.user);
}
get user$() {
return this._user$.asObservable();
}
loadUser() {
return this.storage.get('user').then(
((user: string): Object => {
this.dataStore.user = JSON.parse(user);
this._user$.next(this.dataStore.user);
return this.dataStore.user;
})
);
}
login(accessToken: string) {
return this.http
.post('http://localhost:3000/login', { access_token: accessToken })
.retry(2)
.map((res: Response): any => res.json());
}
logout(): void {
this.storage.remove('user');
}
}
To authenticate I call the login() function and set the user data if everything ok.
this.userService.login(this.data.accessToken)
.subscribe(
(user: Object) => {
this.userService.user$ = user;
this.nav.setRoot(EventListComponent);
},
(error: Object) => console.log(error)
);
I feel it is better set the user data inside the service. I could do the following:
login(accessToken: string) {
return this.http
.post('http://localhost:3000/login', {
access_token: accessToken
})
.retry(2)
.map((res: Response): any => res.json())
.subscribe(
(user: Object) => {
this.userService.user$ = user;
this.nav.setRoot(EventListComponent);
},
(error: Object) => console.log(error)
);
}
But I won't be able to subscribe to the login() function in the component since it's already subscribed. How could I redirect the user if everything ok or show an alert if anything goes wrong in the component but setting the user inside the service?
In the main component I load the user data and set the rootPage:
this.userService.loadUser().then(
(user: Object) => this.rootPage = EventListComponent,
(error: Object) => this.rootPage = LoginComponent
);
I thought that calling the loadUser() function at this time I would not have to call it again, but I have to call it in all components that I need the user data:
this.user = this.userService.user$;
this.userService.loadUser();
I don't think the service is the way it should, what could I improve? Is there any better way to achieve what I want? Any example or idea?