Node.JS dns.reverse multiple nameservers - node.js

I have an issue with my Node.JS application.
For some reason, dns.reverse is only working with the first nameserver specified and ignoring the second one.
I have the following piece of code:
import Resolver from '~/classes/resolver';
Resolver.addNameServer('192.168.2.1', '192.168.1.254').boot();
Resolver.hostnameFromIP('192.168.1.30').then((hostname: string) => {
console.log(hostname);
}).catch((error) => {
console.log(error); // We are going to end up here as the 192.168.2.1 is the first and only nameserver which will be queried
});
And here is Resolver class:
import q, { Deferred } from 'q';
import dns from 'dns';
import { ResolverInterface } from '~/classes/resolver/interfaces';
/**
* Resolver class
*/
class Resolver implements ResolverInterface {
/**
* List of nameservers used for hostname resolution
* #type { string[] }
*/
public nameservers: string[] = [];
/**
* Add nameserver for resolver to use
* #param { string } nameserver
* #return { Resolver }
*/
public addNameServer(...nameserver: string[]) : Resolver {
this.nameservers.push(...nameserver);
return this;
}
/**
* Initialize resolver
* #return { void }
*/
public boot() : void {
if (this.nameservers.length === 0) {
this.initializeWithDefaultNameServers();
}
dns.setServers(this.nameservers);
}
/**
* Resolve hostname from the IP address
* #param { string } ip
* #return { q.Promise<string> }
*/
public hostnameFromIP(ip: string) : q.Promise<string> {
const deferred: Deferred<any> = q.defer();
dns.reverse(ip, (error: NodeJS.ErrnoException | null, hostnames: string[]) => {
const defaultErrorMessage: string = 'Unable to resolve hostname';
if (error) {
return deferred.reject(defaultErrorMessage);
}
let hostname: string = hostnames.length === 0 ? defaultErrorMessage : hostnames[0];
hostname = hostname.replace('.localdomain', '').trim();
deferred.resolve(hostname);
});
return deferred.promise as q.Promise<string>;
}
/**
* Initialize resolver with default nameservers
* #return { void }
* #private
*/
private initializeWithDefaultNameServers() : void {
const nameservers: string[] = [
'192.168.0.1',
'192.168.1.1',
'192.168.2.1',
];
nameservers.forEach((nameserver: string) : void => {
this.nameservers.push(nameserver);
});
}
}
export default new Resolver;
Expected behavior:
Application should go through all specified nameservers to resolve the hostname for specified IP address
Actual behavior:
Depending on which nameserver is first, only that nameserver will be queried for the hostname.
If 192.168.2.1 is first, i can query data for 192.168.2.10, but i cannot do that for 192.168.1.30.
If 192.168.1.254 is first, i can query data for 192.168.1.30, but i cannot do that for 192.168.2.10.
Is there a way to use all specified nameservers while doing reverse hostname lookup using dns.reverse in Node.JS?
Thanks for help to Jorge Fuentes González, here is the version i've ended up using, at least it works for 2 nameservers.
/**
* Resolve hostname from IP address
* #param { string } ip
* #return { Promise<string> }
*/
public async hostnameFromIP(ip: string) : Promise<string> {
return await this.resolveForHostnameFromIP(ip)
.then((hostname: string): string => hostname)
.catch(async (error: string): Promise<string> => {
const indexOf: number = this.nameservers.indexOf(this.currentNameServer);
const newNameserverIndex: number = indexOf + 1;
if (newNameserverIndex <= this.nameservers.length - 1) {
this.currentNameServer = this.nameservers[indexOf + 1];
this.setCurrentNameServerValue();
return await this.hostnameFromIP(ip).then((hostname: string): string => hostname);
}
return error;
});
}
/**
* Helper method to resolve hostname from the IP address
* #param { string } ip
* #return { q.Promise<string> }
*/
private resolveForHostnameFromIP(ip: string) : q.Promise<string> {
const deferred: Deferred<any> = q.defer();
this.resolver.reverse(ip, (error: NodeJS.ErrnoException | null, hostnames: string[]) => {
const defaultErrorMessage: string = 'Unable to resolve hostname';
if (error) {
return deferred.reject(defaultErrorMessage);
} else {
let hostname: string = hostnames.length === 0 ? defaultErrorMessage : hostnames[0];
hostname = hostname.replace('.localdomain', '').trim();
deferred.resolve(hostname);
}
});
return deferred.promise as q.Promise<string>;
}
/**
* Update resolver configuration with current name server
* #return { void }
* #private
*/
private setCurrentNameServerValue() : void {
this.resolver.setServers([this.currentNameServer]);
};

Sorry, got the question wrong so this part is not important. Proper answer below. Btw keeping it here just in case someone has a problem like this:
That's not how DNS server lists work in almost any platform. If you look in your system network settings (Windows, Mac or Linux), surely you will see 2 nameservers (as is the default amount all ISPs provide). That's because there is a main server and a fallback server if the main one fails. You system won't return 2 different IPs when you are browsing Internet. That's weird. What your browser want is just an IP to connect to, not a list of IPs. NodeJS works the same. The rest of nameservers are just fallback ones just in case the main one fails.
Now the proper part:
When a nameserver replies with NOTFOUND, nodejs will not continue to the next nameserver, as is actually a working nameserver. Fallback nameservers are only used when the nameserver fails (with a weird error/response or a timeout. Documentation link below with this info).
To achieve what you want you need to set your nameserver, make your query, wait for the response, set the next nameserver, make your query, etc... with your current code must be an easy task. Looks pretty.
Be careful as you must not change the nameservers when a DNS query is in progress as you can read in the documentation.

Related

google socialite laravel 8 return after authentication a blank screen

when i am tring to authenticate with google account it returns an empty string i don't know why so can u help me find this error i made the all steps requiring to do the authentication it returns callback with an empty string and my client_id and client secret are correct
here is my code google controller
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;
use Exception;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
class GoogleController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function redirectToGoogle()
{
return Socialite::driver('google')->redirect();
}
/**
* Create a new controller instance.
*
* #return void
*/
public function handleGoogleCallback()
{
try {
$user = Socialite::driver('google')->user();
$finduser = User::where('social_id', $user->id)->first();
if($finduser){
Auth::login($finduser);
return redirect()->intended('dashboard');
}else{
$newUser = User::create([
'name' => $user->name,
'email' => $user->email,
'social_id'=> $user->id,
'social_type'=> 'google',
'password' => encrypt('123456dummy')
]);
Auth::login($newUser);
return redirect()->intended('dashboard');
}
} catch (Exception $e) {
dd($e->getMessage());
}
}
}
and my routes
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
Route::middleware([
'auth:sanctum',
config('jetstream.auth_session'),
'verified'
])->group(function () {
Route::get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
});
Route::get('auth/google', [App\Http\Controllers\GoogleController::class, 'redirectToGoogle']);
Route::get('auth/google/callback', [App\Http\Controllers\GoogleController::class, 'handleGoogleCallback']);
my config/services.php
'google' => [
'client_id' => '982061662199-bns94j425f1cgq7p8b0eo4lctjn83e4e.apps.googleusercontent.com',
'client_secret' => 'GOCSPX-9AFn2bhebi2yPUpCU8_LIzfb',
'redirect' => 'http://localhost:8000/auth/google/callback',
],

Chainlink API call job not fulfilled - tutorial

I have been trying to learn how to make a GET request using Chainlink.
I was watching this tutorial: https://www.youtube.com/watch?v=ay4rXZhAefs and I used the exact same code that they used in the video and I also followed the same steps using Remix (I have my metamask (Kovan) open with enough ETH and LINK and I also funded my contract with enough LINK). However after I call requestVolumeData() the volume variable doesn't update and stays 0.
I have tried to use different networks and oracles/jobs, but I seem to run into the same issue over and over again. Any help would be appreciated.
pragma solidity ^0.6.0;
import "#chainlink/contracts/src/v0.6/ChainlinkClient.sol";
/**
* THIS IS AN EXAMPLE CONTRACT WHICH USES HARDCODED VALUES FOR CLARITY.
* PLEASE DO NOT USE THIS CODE IN PRODUCTION.
*/
contract APIConsumer is ChainlinkClient {
using Chainlink for Chainlink.Request;
uint256 public volume;
address private oracle;
bytes32 private jobId;
uint256 private fee;
/**
* Network: Kovan
* Oracle: 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e
* Job ID: 29fa9aa13bf1468788b7cc4a500a45b8
* Fee: 0.1 LINK
*/
constructor() public {
setPublicChainlinkToken();
oracle = 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e;
jobId = "29fa9aa13bf1468788b7cc4a500a45b8";
fee = 0.1 * 10 ** 18; // (Varies by network and job)
}
/**
* Create a Chainlink request to retrieve API response, find the target
* data, then multiply by 1000000000000000000 (to remove decimal places from data).
*/
function requestVolumeData() public returns (bytes32 requestId)
{
Chainlink.Request memory request = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);
// Set the URL to perform the GET request on
request.add("get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD");
// Set the path to find the desired data in the API response, where the response format is:
// {"RAW":
// {"ETH":
// {"USD":
// {
// "VOLUME24HOUR": xxx.xxx,
// }
// }
// }
// }
request.add("path", "RAW.ETH.USD.VOLUME24HOUR");
// Multiply the result by 1000000000000000000 to remove decimals
int timesAmount = 10**18;
request.addInt("times", timesAmount);
// Sends the request
return sendChainlinkRequestTo(oracle, request, fee);
}
/**
* Receive the response in the form of uint256
*/
function fulfill(bytes32 _requestId, uint256 _volume) public recordChainlinkFulfillment(_requestId)
{
volume = _volume;
}
// function withdrawLink() external {} - Implement a withdraw function to avoid locking your LINK in the contract
}
Are you giving some time to let the node fetch the request? When you do this example, you usually have to click the requestVolumeData() button, multiple times, waiting for the node to fulfill the request. Less than two minutes usually.
Here's another more recent version of that example contract. It's nearly the same.
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.6;
import "#chainlink/contracts/src/v0.6/ChainlinkClient.sol";
contract APIConsumer is ChainlinkClient {
uint256 public volume;
address private oracle;
bytes32 private jobId;
uint256 private fee;
/**
* Network: Kovan
* Oracle: 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e
* Job ID: 29fa9aa13bf1468788b7cc4a500a45b8
* Fee: 0.1 LINK
*/
constructor(address _oracle, string memory _jobId, uint256 _fee, address _link) public {
if (_link == address(0)) {
setPublicChainlinkToken();
} else {
setChainlinkToken(_link);
}
// oracle = 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e;
// jobId = "29fa9aa13bf1468788b7cc4a500a45b8";
// fee = 0.1 * 10 ** 18; // 0.1 LINK
oracle = _oracle;
jobId = stringToBytes32(_jobId);
fee = _fee;
}
/**
* Create a Chainlink request to retrieve API response, find the target
* data, then multiply by 1000000000000000000 (to remove decimal places from data).
*/
function requestVolumeData() public returns (bytes32 requestId)
{
Chainlink.Request memory request = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);
// Set the URL to perform the GET request on
request.add("get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD");
// Set the path to find the desired data in the API response, where the response format is:
// {"RAW":
// {"ETH":
// {"USD":
// {
// "VOLUME24HOUR": xxx.xxx,
// }
// }
// }
// }
request.add("path", "RAW.ETH.USD.VOLUME24HOUR");
// Multiply the result by 1000000000000000000 to remove decimals
int timesAmount = 10**18;
request.addInt("times", timesAmount);
// Sends the request
return sendChainlinkRequestTo(oracle, request, fee);
}
/**
* Receive the response in the form of uint256
*/
function fulfill(bytes32 _requestId, uint256 _volume) public recordChainlinkFulfillment(_requestId)
{
volume = _volume;
}
function stringToBytes32(string memory source) public pure returns (bytes32 result) {
bytes memory tempEmptyStringTest = bytes(source);
if (tempEmptyStringTest.length == 0) {
return 0x0;
}
assembly {
result := mload(add(source, 32))
}
}
}

Electron - How to create deep-linking on linux

I have an ElectronJS project and I use the protocols (deep-link) in this one. It's work on MacOS and Windows but on Linux I can't understand how to create this protocol.
I have looked in the ElectronJS documentation as well as on the web for issues etc. but I can't figure out how to initialize protocol on Linux. All I want is to achieve, as I have succeeded on MacOS and Windows, a protocol to interact with the app in deep-link.
Code that works on MacOS and Windows :
// main.ts
// –– B ––– PROTOCOL HANDLER –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
ProtocolUtils.setDefaultProtocolClient();
// eslint-disable-next-line default-case
switch (process.platform) {
case 'darwin':
ProtocolUtils.setProtocolHandlerOSX();
break;
case 'win32':
ProtocolUtils.setProtocolHandlerWin32();
break;
}
// –– E ––– PROTOCOL HANDLER –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// protocol.ts
export abstract class ProtocolUtils {
/**
* #description Create default protocole for call this app.
* Ex : in your browser => myapp://test
*/
public static setDefaultProtocolClient(): void {
if (!app.isDefaultProtocolClient('myapp')) {
// Define custom protocol handler.
// Deep linking works on packaged versions of the application!
app.setAsDefaultProtocolClient('myapp');
}
}
/**
* #description Create logic (WIN32) for open url from protocol
*/
public static setProtocolHandlerWin32(): void {
// Force Single Instance Application on win32
const gotTheLock = app.requestSingleInstanceLock();
app.on('second-instance', (e: Electron.Event, argv: string[]) => {
// Someone tried to run a second instance, we should focus our window.
if (MainWindow.mainWindow) {
if (MainWindow.mainWindow.isMinimized()) MainWindow.mainWindow.restore();
MainWindow.mainWindow.focus();
} else {
MainWindow.openMainWindow(); // Open main windows
}
app.whenReady().then(() => {
MainWindow.mainWindow.loadURL(this._getDeepLinkUrlForWin32(argv)); // Load URL in WebApp
});
});
if (gotTheLock) {
app.whenReady().then(() => {
MainWindow.openMainWindow(); // Open main windows
MainWindow.mainWindow.loadURL(this._getDeepLinkUrlForWin32()); // Load URL in WebApp
});
} else {
app.quit();
}
}
/**
* #description Create logic (OSX) for open url from protocol
*/
public static setProtocolHandlerOSX(): void {
app.on('open-url', (event: Electron.Event, url: string) => {
event.preventDefault();
app.whenReady().then(() => {
MainWindow.openMainWindow(); // Open main windows
MainWindow.mainWindow.loadURL(this._getUrlToLoad(url)); // Load URL in WebApp
});
});
}
/**
* #description Format url to load in mainWindow
*/
private static _getUrlToLoad(url: string): string {
// Ex: url = myapp://deep-link/test?params1=paramValue
// Ex: Split for remove myapp:// and get deep-link/test?params1=paramValue
const urlSplitted = url.split('//');
// Generate URL to load in WebApp.
// Ex: file://path/index.html#deep-link/test?params1=paramValue
const urlToLoad = format({
pathname: Env.BUILDED_WEBAPP_INDEX_PATH,
protocol: 'file:',
slashes: true,
hash: `#${urlSplitted[1]}`,
});
return urlToLoad;
}
/**
* #description Resolve deep link url for windows from process argv
*/
private static _getDeepLinkUrlForWin32(argv?: string[]): string {
let url: string;
const newArgv: string[] = !isNil(argv) ? argv : process.argv;
// Protocol handler for win32
// argv: An array of the second instance’s (command line / deep linked) arguments
if (process.platform === 'win32') {
// Get url form precess.argv
newArgv.forEach((arg) => {
if (/myapp:\/\//.test(arg)) {
url = arg;
}
});
if (!isNil(url)) {
return this._getUrlToLoad(url); // Load URL in WebApp
} else if (!isNil(argv) && isNil(url)) {
throw new Error('URL is undefined');
}
}
}
}
I have no worries for macOS and windows, but on linux the protocol does not exist even with the line :
ProtocolUtils.setDefaultProtocolClient(); who is responsible for creating the myapp: // protocol...
When I run this command : xdg-open myapp://deep-link/test?toto=titi An error tells me that this protocol does not exist
If anyone has an example for me to configure on Linux or can just help me ?
Thanks
Ok i have found the solution !
First, we removed electron-forge and replaced it with electron-builder (cf doc).
Then after reading a lot of documentation for deep links on Linux, Examples of documentation :
Desktop file
Specification of desktop entry
Electron builder desktop file
And my solution is :
# electron-builder.yml
appId: com.myapp.myapp
productName: myapp
directories:
output: out
linux:
icon: src/assets/icons/app/icon#256x256.png
category: Utility
mimeTypes: [x-scheme-handler/myapp] # Define MimeType
desktop: # Define desktop elem
exec: myapp %u # Define Exec
target:
- target: deb
arch:
- x64
So i defined the MimeType with the name of my protocol here myapp which could give:
myapp://toto?foo=bar
And in my desktop file define Exec with myapp %u because %u => A single URL. Local files may either be passed as file: URLs or as file path. (cf doc)
And for finish in my main.ts and protocol.utils.ts:
// main.ts
// –– B ––– PROTOCOL HANDLER –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
ProtocolUtils.setDefaultProtocolClient();
switch (process.platform) {
case 'darwin':
ProtocolUtils.setProtocolHandlerOSX();
break;
case 'linux':
case 'win32':
ProtocolUtils.setProtocolHandlerWindowsLinux();
break;
default:
throw new Error('Process platform is undefined');
}
// –– E ––– PROTOCOL HANDLER –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// protocol.utils.ts
export abstract class ProtocolUtils {
public static setDefaultProtocolClient(): void {
if (!app.isDefaultProtocolClient('myapp')) {
// Define custom protocol handler.
// Deep linking works on packaged versions of the application!
app.setAsDefaultProtocolClient('myapp');
}
}
/**
* #description Create logic (WIN32 and Linux) for open url from protocol
*/
public static setProtocolHandlerWindowsLinux(): void {
// Force Single Instance Application
const gotTheLock = app.requestSingleInstanceLock();
app.on('second-instance', (e: Electron.Event, argv: string[]) => {
// Someone tried to run a second instance, we should focus our window.
if (MainWindow.mainWindow) {
if (MainWindow.mainWindow.isMinimized()) MainWindow.mainWindow.restore();
MainWindow.mainWindow.focus();
} else {
// Open main windows
MainWindow.openMainWindow();
}
app.whenReady().then(() => {
MainWindow.mainWindow.loadURL(this._getDeepLinkUrl(argv));
});
});
if (gotTheLock) {
app.whenReady().then(() => {
// Open main windows
MainWindow.openMainWindow();
MainWindow.mainWindow.loadURL(this._getDeepLinkUrl());
});
} else {
app.quit();
}
}
/**
* #description Create logic (OSX) for open url from protocol
*/
public static setProtocolHandlerOSX(): void {
app.on('open-url', (event: Electron.Event, url: string) => {
event.preventDefault();
app.whenReady().then(() => {
if (!isNil(url)) {
// Open main windows
MainWindow.openMainWindow();
MainWindow.mainWindow.loadURL(this._getUrlToLoad(url));
} else {
this._logInMainWindow({ s: 'URL is undefined', isError: true });
throw new Error('URL is undefined');
}
});
});
}
/**
* #description Format url to load in mainWindow
*/
private static _getUrlToLoad(url: string): string {
// Ex: url = myapp://deep-link/test?params1=paramValue
// Ex: Split for remove myapp:// and get deep-link/test?params1=paramValue
const splittedUrl = url.split('//');
// Generate URL to load in WebApp.
// Ex: file://path/index.html#deep-link/test?params1=paramValue
const urlToLoad = format({
pathname: Env.BUILDED_APP_INDEX_PATH,
protocol: 'file:',
slashes: true,
hash: `#${splittedUrl[1]}`,
});
return urlToLoad;
}
/**
* #description Resolve deep link url for Win32 or Linux from process argv
* #param argv: An array of the second instance’s (command line / deep linked) arguments
*/
private static _getDeepLinkUrl(argv?: string[]): string {
let url: string;
const newArgv: string[] = !isNil(argv) ? argv : process.argv;
// Protocol handler
if (process.platform === 'win32' || process.platform === 'linux') {
// Get url form precess.argv
newArgv.forEach((arg) => {
if (/myapp:\/\//.test(arg)) {
url = arg;
}
});
if (!isNil(url)) {
return this._getUrlToLoad(url);
} else if (!isNil(argv) && isNil(url)) {
this._logInMainWindow({ s: 'URL is undefined', isError: true });
throw new Error('URL is undefined');
}
}
}
And it's WORK :D

Error: Could not find any functions to execute for transaction

my issue is that I am defining transaction in model file and then using it in js script but it throws an error " Error: Could not find any functions to execute for transaction." when i try to execute.It occurs during testing of the code
my mode file
/**
* New model file
*/
/**
* New model file
*/
namespace org.acme.bank
participant accountholder identified by bankid
{
o String bankid
o String firstname
o String lastname
o String address
}
asset acount identified by accno
{
o String accno
o String balance
-->accountholder customer1
}
transaction amountTransfer
{
o String tid
o String amount
-->acount owner1
-->acount owner2
}
my script.js
/**
* Track the trade of a commodity from one trader to another
* #param {org.acme.bank.amountTransfer} Transfer - to trade
* #transactiton
*/
function Transfer(Transfer)
{
var amount1=Transfer.owner1.balance
var amount2=Transfer.owner2.balance
if(Transfer.amount>amount1)
{
return 0;
}else
{
owner1.balance-=Transfer.amount
owner2.balance+=Transfer.amount
return getAssetRegistry('org.acme.bank.acount')
.then(function (assetRegistry) {
return assetRegistry.update(Transfer.owner1);
}).then(function () {
return getAssetRegistry('org.acme.bank.acount');
}).then(function (assetRegistry) {
return assetRegistry.update(Transfer.owner2);
});
}
}
thank you in advance
I found a tiny typo #transactiton in my script.js and I changer it then the error no longer occurs.
I think the code below works as you expected.
/**
* Track the trade of a commodity from one trader to another
* #param {org.acme.bank.amountTransfer} Transfer - to trade
* #transaction
*/
function Transfer(Transfer)
{
var amount1=Transfer.owner1.balance
var amount2=Transfer.owner2.balance
if(Transfer.amount>amount1)
{
return 0;
}else
{
var owner1 = Transfer.owner1
var owner2 = Transfer.owner2
owner1.balance-=Transfer.amount
owner2.balance+=Transfer.amount
return getAssetRegistry('org.acme.bank.acount')
.then(function (assetRegistry) {
return assetRegistry.update(owner1);
}).then(function () {
return getAssetRegistry('org.acme.bank.acount');
}).then(function (assetRegistry) {
return assetRegistry.update(owner2);
});
}
}
Now balance and amount field's type in your model are changed to Integer.

How to overload mongoose model instance in nodejs & Typescript

I'm trying to change the save() method, but I don't find where I can overload it. I use typescript and node.js.
For the moment, I have a UserModel that contains a mongoose.Schema and a mongoose.Model.
When I call UserModel.getModel() I retrieve the mongoose.Model from the UserModel.
I basically use a DAO to retrieve the Model class object.
user = message.getDataByKey('user');
user.save(function(err, data) {
// do stuff
});
I want to overload automatically the user object to use my own .save() method to check if there is error and always handle them by the same way.
When I set the Model, I do it like that:
public static model: any = model.Models.MongooseModel.getNewInstance(UserModel.modelName, UserModel._schema);
And in the parent:
public static getNewInstance(modelName, schema){
var Model: any = mongoose.model(modelName, schema);
// Overload methods.
//console.log(new Model());
// Return overloaded Model class.
return Model;
}
I would like to know if there is any way to overload the Model to make sure that each new instance from it will have my own .save method.
I thought use the statics/methods (methods actually, I guess) but it's empty or I know that the final object will have save/remove/update methods. So I don't know why it's not already into the object, I tried to console.log(Model and new Model()) but no save() method.
So I'm a little desappointed, maybe I missed something.
The fact is, I can't update directly the new Model() because they will be created later, in another context, I need to update the Model directly to make sure that the new instance from this model will have my extra function.
And I don't want to rewrite the basic .save() method, I just want to overload it to add extra validation.
Any idea? I'm kinda lost here, it's not that easy. Thx.
I found a solution to do so, I'm using typescript so I'll post both .ts and .js to everybody understand.
I use CommonJs compilation
Model.ts (Super model, parent of all models)
///<reference path='./../../lib/def/defLoader.d.ts'/>
/**
* Package that contains all Models used to interact with the database.
* TODO Use options http://mongoosejs.com/docs/guide.html
*/
export module Models {
/**
* Interface for all Models, except the parent class.
*/
export interface IModel{
/**
* Name of the model.
* It's a helper to always get the name, from instance or static.
* MUST start by uppercase letter!
*/
modelName: string;
/**
* Contains the static value of the public schema as object.
* It's a helper to always get the schema, from instance or static.
*/
schema: mongoose.Schema;
/**
* Contains the static value of the object used to manipulate an instance of the model.
* It's a helper to always get the model, from instance or static.
*/
model: any;
}
/**
* Parent class for all models.
* A model contains a mongoose schema and a mongoose model and other things.
*/
export class Model{
/**
* Suffix used to load automatically models.
*/
public static suffix: string = 'Model';
/**
* Suffix used to load automatically models.
* It's a helper to always get the schema, from instance or static.
*/
public suffix: string;
/**
* Name of the model.
* MUST start by uppercase letter!
*/
public static modelName: string = '';
/**
* Readable schema as object.
*/
public static schema: any;
/**
* Schema as mongoose Schema type.
*/
public static Schema: mongoose.Schema;
/**
* The mongoose model that uses the mongoose schema.
*/
public static model: any;
/**
* Use static values as instance values.
*/
constructor(){
// Use static values as instance values.
this.suffix = Model.suffix;
}
/**
* Returns a new mongoose.Schema customized instance.
* #param ChildModel Child model that made the call.
* #returns {*}
* #see http://mongoosejs.com/docs/2.7.x/docs/methods-statics.html
*/
public static getNewSchemaInstance(ChildModel): mongoose.Schema{
var schema: any = new mongoose.Schema(ChildModel.schema, {collection: ChildModel.modelName.toLowerCase()});
// Overload methods.
//schema.methods.toObject = function(callback){}
// Return overloaded instance.
return schema;
}
/**
* Retrieves a new Model instance and overload it to add statics methods available for all Models.
* #param ChildModel
* #returns {*}
* #see http://mongoosejs.com/docs/2.7.x/docs/methods-statics.html
*/
public static getNewModelInstance(ChildModel): any{
// Get the Model class.
var Model: any = mongoose.model(ChildModel.modelName, ChildModel.Schema);
/**
**************************************************************************************************
************************ Extended Model static methods for all Models ****************************
**************************************************************************************************
*/
/**
* Handler for all database/mongoose errors.
* #param err Error.
* #param data Data. Contains the model and the emitter. (+ more)
* #param callback Callback function to execute.
*/
Model.errorHandler = (err: any, data: any, callback: (message: (any) => any) => any) => {
// Extract data.
var _Model = data.model;
var __function = data.__function;
var __line = data.__line;
// Will contains the error.
var message:any = [];
// Mongo error.
if(err && err.name && err.name == 'MongoError'){
var _err = MongoError.parseMongoError(err);
if(err.code == 11000){
// Duplicate key on create.
message[0] = '__19';
message[1] = [_err.value, _err.field];
}else if(err.code == 11001){
// Duplicate key on update.
message[0] = '__20';
message[1] = [_err.value, _err.field];
}else{
// Non-managed mongo error.
if(dev()){
// Return not only the message but also some information about the error.
message[0] = [];
// Message. [0][1] could be args.
message[0][0] = '__21';
// Data.
message[1] = {
err: err,
model: _Model.modelName
};
}else{
message = '__21';
}
}
fs.appendFile(__config.path.base + __config.mongo.error.log, new Date() + ': ' + JSON.stringify({error: err, model: _Model.modelName, _err: _err}) + '\n');
}else if(err && err.name && err.name == 'ValidationError'){
// Validation error from mongoose.
var _err = MongoError.parseValidationError(err);
message[0] = [];
// Message. [0][1] could be args.
message[0][0] = '__24';
message[0][1] = [_err[0].value, _err[0].field, _err[0].type];
if(dev()){
// Will be send as args but not displayed in the message.
message[1] = {
err: _err,
model: _Model.modelName
};
}
fs.appendFile(__config.path.base + __config.mongo.error.log, new Date() + ': ' + JSON.stringify({error: err, model: _Model.modelName, _err: _err}) + '\n');
}else{
// Another error? I don't know if that could happens, but manage it anyway.
message[0] = '__22';
if(dev()){
message[1] = [err, _Model.modelName];// Will be send as args but not displayed in the message.
}
fs.appendFile(__config.path.base + __config.mongo.error.log, new Date() + ': ' + JSON.stringify({error: err, model: _Model.modelName}) + '\n');
}
callback(message);// return an error.
};
/**
* Check if the object exists and returns it in this case.
* #param object Object to find.
* #param callback Callback to execute.
* #return
* err Error if it happens. [null]
* found Found object or false.
*/
Model.exists = (object, callback): any => {
// If object is null or false or empty or whatever, don't do the research, the result could be wrong!
if(!object){
callback (null, false);
}else{
Model.findOne(object, function (err, found) {
if (err){
Model.errorHandler(err, ChildModel, callback);
}else if (found){
callback(null, found);
}else{
callback (null, false);
}
});
}
};
// Return overloaded instance.
return Model;
}
}
/**
* Class that manage MongoDb errors, used statically.
*/
export class MongoError{
/**
* Parse a mongo error to returns data from it because Mongo returns really bad errors.
* #param err The mongo error.
* #returns {*}
*/
public static parseMongoError(err): any{
var _err: any = {};
var _message: string = err.err;
if(err.code == 11000 || err.code == 11001){
var message = _message.split(':');
// Get the table where the error was generated.
_err.table = message[1].split('.')[1];
// Get the field name where the error was generated.
_err.field = message[1].split('.')[2].split(' ')[0].replace('$', '');
_err.field = _err.field.substr(0, _err.field.lastIndexOf('_'));
// Get the
_err.value = message[3].split('"')[1].replace('\\', '');
}
return _err;
}
/**
* Parse a mongoose validation error, probably generated during a save/update function.
* #param err The mongoose error.
* #returns {*}
*/
public static parseValidationError(err): any{
var _errors: any = new Array();
var i = 0;
for(var error in err.errors){
_errors[i] = [];
_errors[i]['field'] = err.errors[error]['path'];
_errors[i]['value'] = err.errors[error]['value'];
_errors[i]['type'] = err.errors[error]['type'];
i++;
}
return _errors;
}
}
}
The JS version:
http://pastebin.com/xBTr1ZVe
Error messages (__21, etc.) are:
"__19": "Unable to add the element, the value **_$0** for the field _$1 already exists, it cannot be duplicated.",
"__20": "Unable to update the element, the value **_$0** for the field _$1 already exists, it cannot be duplicated.",
"__21": "Unable to execute the requested operation. Database error 21. Please report to an administrator.",
"__22": "Unable to execute the requested operation. Database error 22. Please report to an administrator.",
"__23": "Validation error, the requested operation was aborted. Please check that all your data are correct and retry. Please report to an administrator. (code 23)",
"__24": "Unable to perform the operation, the value ***_$0** for the field _$1 didn't pass successfully the validation. Error: _$2",
Basically all my models should manage by themself these exception, of course. But if I forgot to do it, I'll get a managed exception, better.
Now I'll post a real Model, UserModel inheriting the parent Model.
///<reference path='./../../lib/def/defLoader.d.ts'/>
import model = require('./Model');
export module Models {
/**
* Model used to manage users.
* The model is primary static, but, to make it easy to use, some things are also stored for each instance.
* That allows the code to use both Model or instance of Model such as:
* Model.schema
* model.Schema
*/
export class UserModel extends model.Models.Model implements model.Models.IModel{
/**
*************************************************************************************************
****************************** Public methods & attributes **************************************
*************************************************************************************************
*/
/**
* Name of the model.
* MUST start by uppercase letter!
*/
public static modelName: string = 'User';
/**
* Readable schema as object.
*/
public static schema: any = require('../schemas/userSchema.js');
/**
* Schema as mongoose Schema type.
*/
public static Schema: mongoose.Schema = model.Models.Model.getNewSchemaInstance(UserModel);
/**
* The mongoose Model that uses the mongoose schema.
*/
public static model: any = model.Models.Model.getNewModelInstance(UserModel);
/**
* Helpers to always get the property, from instance or static.
*/
public modelName: string = UserModel.modelName;
public schema: mongoose.Schema = UserModel.schema;
public model: mongoose.Model<any> = UserModel.model;
/**
*************************************************************************************************
***************************** Extended methods & attributes **************************************
*************************************************************************************************
*/
/**
* These fields are protected, the user password is required to access to them.
* These fields are basically shared between applications.
* #private
*/
private static _protectedFields: string[] = [
'login',
'email'
];
/**
* Method to use to hash the user password.
*/
private static _passwordHashMethod: string = 'sha256';
/**
* Digest to use to hash the user password.
*/
private static _passwordDigest: string = 'hex';
/**
* Returns the protected fields.
* #returns {string[]}
*/
public static getProtectedFields(): string[]{
return this._protectedFields;
}
/**
* Hash a user password depending on the password hash configuration. Currently SHA256 in hexadecimal.
* Assuming crypto is global.
* #param password User password.
* #returns {string} Hashed password.
*/
public static hashPassword(password: string): string{
return crypto
.createHash(UserModel._passwordHashMethod)
.update(password)
.digest(UserModel._passwordDigest)
}
}
/**
* Don't forget that some methods such as exists() are written in the Model class and available for all Models.
* The following methods belong ONLY to the mongoose model instance, not to the Model class itself!
*
*************************************************************************************************
******************************** Extended Model methods *****************************************
*************************************************************************************************
*/
/**
* Connect a user to the game.
* #param user User to check. {}
* #param callback Callback to execute.
*/
UserModel.model.checkAuthentication = (user, callback) => {
// Force to provide login and password.
UserModel.model.exists({login: user.login, password: UserModel.hashPassword(user.password)}, function(err, userFound){
// Load public profile.
UserModel.model._getProtectedInformation(userFound, function(userPublic){
// Provides only public fields.
callback(new __message("__17", {err: err, user: userPublic}, !err && userFound ? true: false));
});
});
};
/**
* Get the protected fields for the found user.
* #param user User to find.
* #param callback Callback to execute.
*/
UserModel.model.getProtectedInformation = (user, callback) => {
// We are looking for an unique user.
UserModel.model.exists(user, function(err, userFound){
if(err){
UserModel.model.errorHandler(err, UserModel, callback);
}else{
// Load public profile.
UserModel.model._getProtectedInformation(userFound, function(userPublic){
// Provides only public fields.
callback(new __message('', {err: err, user: userPublic}, err ? false: true));
});
}
});
};
/**
* Get the protected fields of a user.
* #param user Instance of model.
* #param callback Callback to execute.
* #private
*/
UserModel.model.hashPassword = (user, callback): any => {
var err = false;
if(user && user.password){
user.password = UserModel.hashPassword(user.password);
}else{
err = true;
}
callback(new __message(err ? '__18': '', {user: user}, err ? false: true));
};
/**
*************************************************************************************************
*************************** Methods to use only locally (private) *******************************
*************************************************************************************************
*/
/**
* Get the protected fields of a user.
* #param user Instance of model.
* #param callback Callback to execute.
* #private
*/
UserModel.model._getProtectedInformation = (user, callback): any => {
var userPublic = {};
// Get fields to share.
var publicFields = UserModel.getProtectedFields();
// Fill the userPublic var with public fields only.
for(var field in publicFields){
userPublic[publicFields[field]] = user[publicFields[field]];
}
callback(userPublic);
};
}
The JS version:
http://pastebin.com/0hiaMH25
The schema:
/**
* Schema ued to create a user.
* #see http://mongoosejs.com/docs/2.7.x/docs/schematypes.html
*/
module.exports = userSchema = {
/**
* User Login, used as id to connect between all our platforms.
*/
login: {
type: 'string',
//match: /^[a-zA-Z0-9_-]{'+userSchema.login.check.minLength+','+userSchema.login.check.maxLength+'}$/,
trim: true,
required: true,
notEmpty: true,
unique: true,
check: {
minLength: 4,
maxLength: 16
}
},
/**
* User email.
*/
email: {
type: 'string',
lowercase: true,
match: /^[a-zA-Z0-9._-]+#[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/,
required: true,
notEmpty: true,
unique: true,
check: {
minLength: 6,
maxLength: 30
}
},
/**
* User private password, the one hashed in SHA512 and stored on the database.
*/
password: {
type: 'string',
required: true,
check: {
length: 128
}
},
/**
* Salt to use to decrypt the password.
*/
passwordSalt: {
type: 'string',
check: {
length: 64
}
},
/**
* Password sent from user interface but hashed before be send on the network.
* Used to basically connect an user or generate the final password.
* Not stored in the DB.
*/
passwordProtected: {
type: 'string',
check: {
length: 64
}
},
/**
* Password wrote by the user on the GUI, not hashed or encrypted.
* Will be encrypted to respect the "passwordProtected" rules.
* Not stored in the DB.
*/
passwordPublic: {
type: 'string',
check: {
minLength: 8,
maxLength: 25
}
},
/**
* User banned status (Temporary of Definitive)
*/
banned: {
temporary : {
type : "number",
default : Date.now
},
definitive: {
type: 'boolean',
default: false
}
},
/**
* User right
*/
right : {
admin : {
type : "boolean",
default : false,
required: true
},
moderator : {
type : "boolean",
default : false,
required: true
}
}
};
So, what the code does?
Basically, in the Model.getNewModelInstance() I bind to the created model the errorHandler method that I will call if I found a DB error in the controller.
**UserController.js**
User.exists({email: user.email}, function(err, emailFound){
// If we got an err => Don't find couple User/pass
if (err) {
User.errorHandler(err, {model: User, __filename: __filename,__function: __function || 'subscription#exists', __line: __line}, function(err){
res.json(__format.response(err));
});
)
});
The __filename and so on are global functions that I use to get the current data, useful to debug. I'm still looking for a way to add this automatically but so far I couldn't. The __function doesn't exists when the function is anonymous. But it helps me to debug.
Any suggestion? That's a lot of piece of code.

Resources