Transaction between 2 repositories in nestjs tyoeorm - nestjs

According to you what would be the procedure for making a transaction between 2 repositories?
Example: I would like to make a transaction with my CompanyRepository & UserRepository because a User belongs to a Company. And I want to be sure that it's all or nothing :wink: Any idea?

You can initialize a transaction based on connection and use EntityManager inside the transaction:
export class DemoService {
constructor(private readonly connection: Connection) {}
save(userData, companyData): void {
this.connection.transaction(async (entityManager: EntityManager) => {
const companyEntityToSave = entityManager.create(CompanyEntity, companyData);
const userEntityToSave = entityManager.create(UserEntity, userData);
const company = entityManager.save(CompanyEntity, companyEntityToSave);
const user = entityManager.save(UserEntity, userEntityToSave);
return await Promise.all([company, user]);
});
}
}
If you want to use repositories:
export class DemoService {
constructor(private readonly connection: Connection) {}
save(): void {
this.connection.transaction(async (entityManager: EntityManager) => {
const companyRepository = entityManager.getRepository(CompanyEntity);
const userRepository = entityManager.getRepository(UserEntity);
// rest code
});
}
}
Btw, it's not only one way to use transaction with few entities, that's just one that I've used last time

Related

Better way for Connections?

Currently, I have these lines of code littered all throughout my app.
Anytime, a GET or INSERT is called these lines are at the beginning of the function. So I feel like they are treated as a re-tryer to the DB. Could these just be put in the dbService class so that I don't have to connect to the?
and for reference: `` looks like this and this is all coming from
Any ideas on a proper way to do this? Would I have to create a re-tryer function in case my DB goes down?
Meet the singleton pattern :
// Wrapper around client to trigger reconnects
class PerpetualClient {
private client: Client = null
private async function createClient(): Client {
// However you create client
}
public getClient(){
return self.client;
}
public static async getPerpetualClient() : PerpetualClient{
let perpetualClient = new PerpetualClient();
const client = await PerpetualClient.createClient();
perpertualClient.client = client;
const reconnect = async ()=>{
const client = await PerpetualClient.createClient();
perpertualClient.client = client;
client.on('end',reconnect)
}
client.on('end',reconnect)
return perpertualClient;
}
}
// Class to enforce the single instantiation
class DBSingleton {
private reader: PerpetualClient = null
private writer: PerpetualClient = null
private static singleInstance: DBSingleton
private async startDB(){
DBSingleton.singleInstance = DBSingleton()
db.reader = await PerpetualClient.getPerpetualClient();
db.writer = await PerpetualClient.getPerpetualClient();
}
public async getDB(){
if(!DBSingleton.singleInstance){
await DBSingleton.startDB()
}
return DBSingleton.singleInstance;
}
}

How to use a Future in a StreamBuilder in Flutter [duplicate]

This question already has answers here:
What is a Future and how do I use it?
(6 answers)
Closed 2 years ago.
I'm trying to build my first mobile application with flutter and firebase.
Error:
The argument type 'Future Function()' can't be assigned to the parameter type 'String'
class ShowUserData extends StatefulWidget {
#override
_ShowUserDataState createState() => _ShowUserDataState();
}
class _ShowUserDataState extends State<ShowUserData> {
final email = () {
final userEmail =
FirebaseAuth.instance.currentUser().then((user) => user.email);
print('userEmail + $userEmail');
return userEmail;
};
#override
Widget build(BuildContext context) {
return StreamBuilder(
// stream: user.getUserData().asStream(),
stream: Firestore.instance.collection('users').document(email).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text("Loading");
}
print('snapshot + $snapshot');
var userDocument = snapshot.data;
print('userDocument + $userDocument');
return Text(userDocument['firstName']);
});
}
}
For the first part of your code, consider something like this:
Future<String> getUserEmail() async {
final userEmail =
FirebaseUser user = await FirebaseAuth.instance.currentUser();
String userEmail = user.email;
print('userEmail + $userEmail');
return userEmail;
};
That should solve that error (hence answering your question). Although the rest of the code will still fail. You should read up on futures and async programming.
Now my suggested solution for the whole thing.
Since you have 2 different async sources that create your stream, you might want to consider creating your own Stream! (Yes, streams are powerful like that).
In sequence, you’re wanting to
await on the future: .currentUser()
use the result of that to create your firestore snapshot stream
Try something like this
class ShowUserData extends StatefulWidget {
#override
_ShowUserDataState createState() => _ShowUserDataState();
}
class _ShowUserDataState extends State<ShowUserData> {
Stream currentUserStream() async* {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
String userEmail = user.email;
yield* Firestore.instance.collection('users').document(userEmail).snapshots();
};
#override
Widget build(BuildContext context) {
return StreamBuilder(
// stream: user.getUserData().asStream(),
stream: currentUserStream(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text("Loading");
}
print('snapshot + $snapshot');
var userDocument = snapshot.data;
print('userDocument + $userDocument');
return Text(userDocument['firstName']);
});
}
}

Strongloop/loopback 4: how to disable authentication for Explorer component over REST?

I have followed and set up via #loopback/authentication
But the authentication is added to sequence.ts for all calls.
I am unable to skip authentication for Explorer component
My open source repo: opencommerce/questionnaire-server
Details:
application.ts has
import {BootMixin} from '#loopback/boot';
import {ApplicationConfig} from '#loopback/core';
import {
RestExplorerBindings,
RestExplorerComponent,
} from '#loopback/rest-explorer';
import {RepositoryMixin} from '#loopback/repository';
import {RestApplication} from '#loopback/rest';
import {ServiceMixin} from '#loopback/service-proxy';
import {
AuthenticationComponent,
AuthenticationBindings,
} from '#loopback/authentication';
import {MyAuthStrategyProvider} from './providers';
import * as path from 'path';
import {MySequence} from './sequence';
export class QuestionnaireApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
super(options);
// Set up the custom sequence
this.sequence(MySequence);
// Set up default home page
this.static('/', path.join(__dirname, '../../public'));
// Customize #loopback/rest-explorer configuration here
this.bind(RestExplorerBindings.CONFIG).to({
path: '/explorer',
});
this.component(RestExplorerComponent);
this.projectRoot = __dirname;
this.component(AuthenticationComponent);
this.bind(AuthenticationBindings.STRATEGY).toProvider(
MyAuthStrategyProvider,
);
// Customize #loopback/boot Booter Conventions here
this.bootOptions = {
controllers: {
// Customize ControllerBooter Conventions here
dirs: ['controllers'],
extensions: ['.controller.js'],
nested: true,
},
};
}
}
sequence.ts has
import {inject} from '#loopback/context';
import {
FindRoute,
InvokeMethod,
ParseParams,
Reject,
RequestContext,
RestBindings,
Send,
SequenceHandler,
} from '#loopback/rest';
import {AuthenticationBindings, AuthenticateFn} from '#loopback/authentication';
const SequenceActions = RestBindings.SequenceActions;
export class MySequence implements SequenceHandler {
constructor(
#inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
#inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,
#inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
#inject(SequenceActions.SEND) public send: Send,
#inject(SequenceActions.REJECT) public reject: Reject,
#inject(AuthenticationBindings.AUTH_ACTION)
protected authenticateRequest: AuthenticateFn,
) {}
async handle(context: RequestContext) {
try {
const {request, response} = context;
const route = this.findRoute(request);
// This is the important line added to the default sequence implementation
await this.authenticateRequest(request);
// Authentication successful, proceed to invoke controller
const args = await this.parseParams(request, route);
const result = await this.invoke(route, args);
this.send(response, result);
} catch (err) {
this.reject(context, err);
}
}
}
Error while accessing / .
Unhandled error in GET /: 500 Error: The key controller.current.ctor was not bound to any value.
at QuestionnaireApplication.getBinding (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/context.js:225:15)
at RestServer.getBinding (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/context.js:221:33)
at RequestContext.getBinding (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/context.js:221:33)
at RequestContext.getValueOrPromise (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/context.js:260:30)
at resolution_session_1.ResolutionSession.runWithInjection.s (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/resolver.js:73:24)
at value_promise_1.tryWithFinally (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/resolution-session.js:89:53)
at Object.tryWithFinally (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/value-promise.js:162:18)
at Function.runWithInjection (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/resolution-session.js:89:32)
at resolve (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/resolver.js:66:59)
at value_promise_1.resolveList (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/resolver.js:144:16)
at Object.resolveList (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/value-promise.js:135:32)
at resolveInjectedArguments (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/resolver.js:128:28)
at Object.instantiateClass (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/resolver.js:37:27)
at Binding._getValue (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/binding.js:338:50)
at resolution_session_1.ResolutionSession.runWithBinding.s (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/binding.js:189:90)
at value_promise_1.tryWithFinally (/opt/lampp7.2/htdocs/opencommerce/questionnaire-server/node_modules/#loopback/context/dist/src/resolution-session.js:69:53)
Your custom sequence does not skip the authentication for static route /.
It can be skipped auth as below:
if (!(route instanceof StaticAssetsRoute)) {
// do your login stuff here
}
Now the updated sequence.ts will be:
import {inject} from '#loopback/context';
import {
FindRoute,
InvokeMethod,
ParseParams,
Reject,
RequestContext,
RestBindings,
Send,
SequenceHandler,
StaticAssetsRoute,
} from '#loopback/rest';
import {AuthenticationBindings, AuthenticateFn} from '#loopback/authentication';
const SequenceActions = RestBindings.SequenceActions;
export class MySequence implements SequenceHandler {
constructor(
#inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
#inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,
#inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
#inject(SequenceActions.SEND) public send: Send,
#inject(SequenceActions.REJECT) public reject: Reject,
#inject(AuthenticationBindings.AUTH_ACTION)
protected authenticateRequest: AuthenticateFn,
) {}
async handle(context: RequestContext) {
try {
const {request, response} = context;
const route = this.findRoute(request);
// This is the important line added to the default sequence implementation
if (!(route instanceof StaticAssetsRoute)) {
await this.authenticateRequest(request);
}
// Authentication successful, proceed to invoke controller
const args = await this.parseParams(request, route);
const result = await this.invoke(route, args);
this.send(response, result);
} catch (err) {
this.reject(context, err);
}
}
}
One possible issue you can have is that you bind the values after setting your sequence. Then the bindings does not exist when Loopback tries to resolve them. You should put something more like this:
this.bind(RestExplorerBindings.CONFIG).to({
path: '/explorer',
});
this.component(RestExplorerComponent);
this.component(AuthenticationComponent);
this.bind(AuthenticationBindings.STRATEGY).toProvider(
MyAuthStrategyProvider,
);
this.sequence(MySequence);
I'm using the following to determine if the route is static.
private static isStaticRoute(route: ResolvedRoute): boolean {
return route.path.search(/^\/explorer\/*/) === 0;
}

How to deal with Context.Done(R value)

I have a msbot chat dialog that I want to have the following behaviour:
user -> get me some info about GARY
bot -> which gary, (prompt: choice options)
user -> gary peskett
bot -> sure, (hero card with gary's contact details)
I have this code
public class CustomerRepository
{
private IList<Customer> _customerList = new List<Customer>
{
new Customer
{
Name = "Gary Peskett"
},
new Customer
{
Name = "Gary Richards"
},
new Customer
{
Name = "Barry White"
}
};
public async Task<IEnumerable<Customer>> GetAll()
{
// usually calls a database (which is why async is on this method)
return _customerList;
}
}
public class XDialog : IDialog
{
private readonly IIntent _intent;
private readonly CustomerRepository _customerRepository;
public XDialog(IIntent intent, CustomerRepository customerRepository)
{
// An intent is decided before this point
_intent = intent;
_customerRepository = customerRepository;
}
public async Task StartAsync(IDialogContext context)
{
// // An intent can provide parameters
string name = _intent.Parameters["Name"] as string;
IEnumerable<Customer> customers = await _customerRepository.GetAll();
IList<Customer> limitedList = customers.Where(x => x.Name.Contains(name)).ToList();
if (limitedList.Any())
{
if (limitedList.Count > 1)
{
PromptDialog.Choice(context, LimitListAgain, limitedList,
"Can you specify which customer you wanted?");
}
else
{
Customer customer = limitedList.FirstOrDefault();
Finish(context, customer);
}
}
else
{
context.Done("No customers have been found");
}
}
private static async Task LimitListAgain(IDialogContext context, IAwaitable<Customer> result)
{
Customer customer = await result;
Finish(context, customer);
}
private static void Finish(IDialogContext context, Customer customer)
{
HeroCard heroCard = new HeroCard
{
Title = customer?.Name
};
context.Done(heroCard);
}
}
What i'm finding is that usually when I do context.Done(STRING) then that is output to the user, and this is really useful to end the dialog. As I want to end with a hero card, its outputing the typename
Microsoft.Bot.Connector.HeroCard
Can anyone help by either explaining a better way to use context.Done(R value) or help me return a hero card to end the dialog?
The dialog is being called with
Chain.PostToChain()
.Select(msg => Task.Run(() => _intentionService.Get(msg.ChannelId, msg.From.Id, msg.Text)).Result)
.Select(intent => _actionDialogFactory.Create(intent)) // returns IDialog based on intent
.Unwrap()
.PostToUser();
I think the problem is a side effect of using Chain.
As you may know, the context.Done doesn't post anything back to the user, it just ends the current dialog with the value provided.
The post to user is effectively happening in the .PostToUser() at the end of your Chain. Now, by looking into the PostToUser's code, I realized that at the end of the game, it's doing a context.PostAsync of item.ToString(), being item the payload provided in the context.Done in this case. See this.
One option (I haven't tested this), could be using .Do instead of .PostToUser() and manually perform what the PostToUserDialog does and finally perform a context.PostAsync() by creating a new IMessageActivity and adding the HeroCard as an attachment.

ASP.net Identity Disable User

Using the new ASP.net Identity in MVC 5, How do we disable a user from logging in? I don't want to delete them, maybe just disable their account for a time period.
Does anyone have any ideas on this as I don't see a status column or anything on the ASPNetUsers table.
await userManager.SetLockoutEnabledAsync(applicationUser.Id, true);
await userManager.SetLockoutEndDateAsync(DateTime.Today.AddYears(10));
Update: As CountZero points out, if you're using v2.1+, then you should try and use the lockout functionality they added first, before trying the solution below. See their blog post for a full sample: http://blogs.msdn.com/b/webdev/archive/2014/08/05/announcing-rtm-of-asp-net-identity-2-1-0.aspx
Version 2.0 has the IUserLockoutStore interface that you can use to lockout users, but the downside is that there is no OOB functionality to actually leverage it beyond the pass-through methods exposed by the UserManager class. For instance, it would be nice if it would actually increment the lockout count as a part of the standard username/password verification process. However, it's fairly trivial to implement yourself.
Step #1: Create a custom user store that implements IUserLockoutStore.
// I'm specifying the TKey generic param here since we use int's for our DB keys
// you may need to customize this for your environment
public class MyUserStore : IUserLockoutStore<MyUser, int>
{
// IUserStore implementation here
public Task<DateTimeOffset> GetLockoutEndDateAsync(MyUser user)
{
//..
}
public Task SetLockoutEndDateAsync(MyUser user, DateTimeOffset lockoutEnd)
{
//..
}
public Task<int> IncrementAccessFailedCountAsync(MyUser user)
{
//..
}
public Task ResetAccessFailedCountAsync(MyUser user)
{
//..
}
public Task<int> GetAccessFailedCountAsync(MyUser user)
{
//..
}
public Task<bool> GetLockoutEnabledAsync(MyUser user)
{
//..
}
public Task SetLockoutEnabledAsync(MyUser user, bool enabled)
{
//..
}
}
Step #2: Instead of UserManager, use the following class in your login/logout actions, passing it an instance of your custom user store.
public class LockingUserManager<TUser, TKey> : UserManager<TUser, TKey>
where TUser : class, IUser<TKey>
where TKey : IEquatable<TKey>
{
private readonly IUserLockoutStore<TUser, TKey> _userLockoutStore;
public LockingUserManager(IUserLockoutStore<TUser, TKey> store)
: base(store)
{
if (store == null) throw new ArgumentNullException("store");
_userLockoutStore = store;
}
public override async Task<TUser> FindAsync(string userName, string password)
{
var user = await FindByNameAsync(userName);
if (user == null) return null;
var isUserLockedOut = await GetLockoutEnabled(user);
if (isUserLockedOut) return user;
var isPasswordValid = await CheckPasswordAsync(user, password);
if (isPasswordValid)
{
await _userLockoutStore.ResetAccessFailedCountAsync(user);
}
else
{
await IncrementAccessFailedCount(user);
user = null;
}
return user;
}
private async Task<bool> GetLockoutEnabled(TUser user)
{
var isLockoutEnabled = await _userLockoutStore.GetLockoutEnabledAsync(user);
if (isLockoutEnabled == false) return false;
var shouldRemoveLockout = DateTime.Now >= await _userLockoutStore.GetLockoutEndDateAsync(user);
if (shouldRemoveLockout)
{
await _userLockoutStore.ResetAccessFailedCountAsync(user);
await _userLockoutStore.SetLockoutEnabledAsync(user, false);
return false;
}
return true;
}
private async Task IncrementAccessFailedCount(TUser user)
{
var accessFailedCount = await _userLockoutStore.IncrementAccessFailedCountAsync(user);
var shouldLockoutUser = accessFailedCount > MaxFailedAccessAttemptsBeforeLockout;
if (shouldLockoutUser)
{
await _userLockoutStore.SetLockoutEnabledAsync(user, true);
var lockoutEndDate = new DateTimeOffset(DateTime.Now + DefaultAccountLockoutTimeSpan);
await _userLockoutStore.SetLockoutEndDateAsync(user, lockoutEndDate);
}
}
}
Example:
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult> Login(string userName, string password)
{
var userManager = new LockingUserManager<MyUser, int>(new MyUserStore())
{
DefaultAccountLockoutTimeSpan = /* get from appSettings */,
MaxFailedAccessAttemptsBeforeLockout = /* get from appSettings */
};
var user = await userManager.FindAsync(userName, password);
if (user == null)
{
// bad username or password; take appropriate action
}
if (await _userManager.GetLockoutEnabledAsync(user.Id))
{
// user is locked out; take appropriate action
}
// username and password are good
// mark user as authenticated and redirect to post-login landing page
}
If you want to manually lock someone out, you can set whatever flag you're checking in MyUserStore.GetLockoutEnabledAsync().
You can have a new class, which should be derived from IdentityUser class. YOu can add a boolean property in the new class and can use this new property of take care per check for login process. I also done it pretty well. I might wanna take a look at : blog
UserManager.RemovePasswordAsync("userId") will effectively disable a user. If the user has no password he will not be able to log in. You will need to set a new password to enable the user again.

Resources