I'm very new to Node/Express and I'm making an appointment system. I want my users to make an appointment for the day they want and my system sends them notification on that exact time. I found "node-schedule" module which is really good for this task but I don't know where to implemented this. Is there anyway to store all tasks in my app.js or is it enough to just create a node-schedule task every time I hit certain end point for example:
router.get('/', function(req, res, next) {
var j = schedule.scheduleJob(date, function(){
send notification();
});
res.send(200);
}
Note: I do not want to run a constant for loop on my sql table to check for dates
You'll need to persist your application data to some form of permanent storage by either writing to local files using something like SQLite, running your own database server (like MongoDB) or using a cloud-based storage service like Amazon SimpleDb.
Each of these options (and many others) have npm modules you can use to read/write/delete persistent data. For examples, see MongoDb, SQLite3, and SimpleDb, all available using npm on npmjs.com.
UPDATE
Per your comment below: Well, you did ask where you could store your scheduled events. ;)
To persist all scheduled events so they survive possible server failure, you'd need to create a storable data structure to represent them and for each event, create a new instance of your representation and store it to your persistent storage (MySQL).
Typically, you'd use something along the lines of:
{
when:DateTime -- timestamp when the event should fire
what:Action -- what this event should do
args:Arguments -- arguments to pass to Action
pending:Boolean=true -- if false, this event has already fired
}
When you initialize your server, you would query your persistent storage for all events where pending===true and using the results, initialize instances of the node-schedule module.
When you need to schedule a new event while your server was running, you'd create a new event representation, write it into persistent storage and create a new instance of node-schedule using it.
Finally, and most importantly for customer happiness, when a scheduled event completes successfully, just before your event handler (the Action mentioned above) completes, it would need to mark the persistent version of the event it is handling as pending:false so you don't fire any event more than once.
So for example:
'use strict';
var scheduler = require('node-schedule');
/**
* Storable Representation of a Scheduled Event
*
* #param {string|Date} when
* #param {string} what
* #param {array.<string>} [args=[]]
* #param {boolean} [pending=true]
*
* #property {Date} PersistentEvent.when - the datetime this event should fire.
* #property {string} PersistentEvent.what - the name of the action to run (must match key of PersistentEvent.Actions)
* #property {array} PersistentEvent.args - args to pass to action event handler.
* #property {boolean} PersistentEvent.pending - if true, this event has not yet fired.
*
* #constructor
*
* #example
*
* var PersistentEvent = require('PersistentEvent'),
* mysql = require('mysql'),
* conn = mysql.createConnection({ ... });
*
* conn.connect();
*
* // at some point when initializing your app...
*
* // assign your persistent storage connection...
* PersistentEvent.setStore(conn);
*
* // load all pending event from persistent storage...
* PersistentEvent.loadAll$(function (err) {
* if (err) {
* throw new Error('failed to load all PersistentEvents: ' + err);
* }
*
* // from this point on, all persistent events are loaded and running.
*
* });
*/
var PersistentEvent = function (when, what, args, pending) {
// initialize
PersistentEvent.Cache.push(this.init({
when: when,
what: what,
args: args,
pending: pending
}));
};
// ==== PersistentEvent Static Methods ====
/**
* Pre-defined action event handlers.
* <p>
* Where the property key will be used to match the PersistentEvent.what property,
* and the property value is a event handler function that accepts an optional
* array of args and a callback (provided by PersistentEvent.prototype.schedule)
* </p>
*
* #property {object}
* #property {function} Actions.doSomething
* #property {function} Actions.doSomethingElse
*
* #static
*/
PersistentEvent.Actions = {
doSomething: function (args, cb) {
// defaults
args = args || [];
// TODO check specific args here ...
var result = true,
err = null;
// do your action here, possibly with passed args
cb(err, result);
},
doSomethingElse: function (args, cb) {
// defaults
args = args || [];
// TODO check specific args here ...
var result = true,
err = null;
// do your action here, possibly with passed args
cb(err, result);
}
};
/**
* Cache of all PersistentEvents
*
* #type {Array.<PersistentEvent>}
* #static
*/
PersistentEvent.Cache = [];
// Data Management
/**
* Connection to persistent storage.
* TODO - This should be abstracted to handle other engines that MySQL.
* #property {object}
* #static
*/
PersistentEvent.StorageConnection = null;
/**
* Sets the storage connection used to persist events.
*
* #param {object} storageConnection
* #static
*/
PersistentEvent.setStore = function (storageConnection) { // set the persistent storage connection
// TODO - check args here...
// Note: this function isn't really needed unless you're using other kinds of storage engines
// where you'd want to test what engine was used and mutate this interface accordingly.
PersistentEvent.StorageConnection = storageConnection;
};
/**
* Saves a PersistentEvent to StorageConnection.
*
* #param {PersistentEvent} event - event to save
* #param {function} cb - callback on complete
* #static
*/
PersistentEvent.save$ = function (event, cb) {
var conn = PersistentEvent.StorageConnection;
if (null === conn) {
throw new Error('requires a StorageConnection');
}
// TODO - check for active connection here...
// TODO - check args here...
conn.query('INSERT INTO TABLE when = :when, what = :what, args = :args, pending = :pending', event, cb);
};
/**
* Loads all PersistentEvents from StorageConnection.
* #param {function} cb -- callback on complete
* #static
*/
PersistentEvent.loadAll$ = function (cb) {
var conn = PersistentEvent.StorageConnection;
if (null === conn) {
throw new Error('requires a StorageConnection');
}
// check for active connection here...
// check args here...
conn.query('QUERY * FROM TABLE WHERE pending = true', function (err, results) {
if (err) {
return cb(err);
}
results.forEach(function (result) {
// TODO: check for existence of required fields here...
var event = new PersistentEvent(result.when, result.what, result.args, true);
event.schedule();
});
cb(null);
});
};
// ==== PersistentEvent Methods ====
/**
* Initialize an instance of PersistentEvent.
*
* #param {object} opts
* #return {PersistentEvent}
*/
Event.prototype.init = function (opts) {
// check args
if ('object' !== typeof opts) {
throw new Error('opts must be an object');
}
// set defaults
opts.args = opts.args || [];
opts.pending = opts.pending || true;
// convert string to Date, if required
if ('string' === typeof opts.when) {
opts.when = new Date(opts.when);
}
// check that opts contains needed properties
if (!opts.when instanceof Date) {
throw new Error('when must be a string representation of a Date or a Date object');
}
if ('string' !== typeof opts.what) {
throw new Error('what must be a string containing an action name');
}
if (!Array.isArray(opts.args)) {
throw new Error('args must be an array');
}
if ('boolean' !== typeof opts.pending) {
throw new Error('pending must be a boolean');
}
// set our properties
var self = this;
Object.keys(opts).forEach(function (key) {
if (opts.hasOwnProperty(key)) {
self = opts[key];
}
});
return this;
};
/**
* Override for Object.toString()
* #returns {string}
*/
PersistentEvent.prototype.toString = function () {
return JSON.stringify(this);
};
/**
* Schedule the event to run.<br/>
* <em>Side-effect: saves event to persistent storage.</em>
*/
PersistentEvent.prototype.schedule = function () {
var self = this,
handler = Actions[this.what];
if ('function' !== typeof handler) {
throw new Error('no handler found for action:' + this.what);
}
PersistentEvent.save$(self, function () {
self._event = scheduler.scheduleJob(self.when, function () {
handler(self.args, function (err, result) {
if (err) {
console.error('event ' + self + ' failed:' + err);
}
self.setComplete();
});
});
});
};
/**
* Sets this event complete.<br/>
* <em>Side-effect: saves event to persistent storage.</em>
*/
PersistentEvent.prototype.setComplete = function () {
var self = this;
delete this._event;
this.pending = false;
PersistentEvent.save$(this, function (err) {
if (err) {
console.error('failed to save event ' + self + ' :' + err);
}
});
};
Note that is a first-pass boilerplate to show you one way of designing a solution to your problem. It will require further effort on your part to run.
You can have a cron job every morning that will pick all the appointments for that day and schedule pushes for them. This way you'll have to query the DB once that too on the time when the server load is minimal.
Related
I would like to get the actual block progress and not the Progress of all the transfers. Currently i don't know how to detect the blockId of each individual transfer. The information on the progress callback im currently retrieving is pointless.
Here's the progress function, contained within ServiceRestProxy.php
Original Function https://github.com/Azure/azure-storage-php/blob/master/azure-storage-common/src/Common/Internal/ServiceRestProxy.php#L99
/**
* Create a Guzzle client for future usage.
*
* #param array $options Optional parameters for the client.
*
* #return Client
*/
private static function createClient(array $options)
{
$verify = true;
//Disable SSL if proxy has been set, and set the proxy in the client.
$proxy = getenv('HTTP_PROXY');
// For testing with Fiddler
// $proxy = 'localhost:8888';
// $verify = false;
if (!empty($proxy)) {
$options['proxy'] = $proxy;
}
if (!empty($options['verify'])) {
$verify = $options['verify'];
}
$downloadTotal = 0;
return (new \GuzzleHttp\Client(
array_merge(
$options,
array(
"defaults" => array(
"allow_redirects" => true,
"exceptions" => true,
"decode_content" => true,
),
'cookies' => true,
'verify' => $verify,
'progress' => function (
$downloadTotal,
$downloadedBytes,
$uploadTotal,
$uploadedBytes
){
// i need to detect which block the progress is for.
echo ("progress: download: {$downloadedBytes}/{$downloadTotal}, upload: {$uploadedBytes}/{$uploadTotal}");
}
)
)
));
}
I got a solution to get each block progress.
I needed to use the Async Function for this. Updated version.
/**
* Send the requests concurrently. Number of concurrency can be modified
* by inserting a new key/value pair with the key 'number_of_concurrency'
* into the $requestOptions of $serviceOptions. Return only the promise.
*
* #param callable $generator the generator function to generate
* request upon fulfillment
* #param int $statusCode The expected status code for each of the
* request generated by generator.
* #param ServiceOptions $options The service options for the concurrent
* requests.
*
* #return \GuzzleHttp\Promise\Promise|\GuzzleHttp\Promise\PromiseInterface
*/
protected function sendConcurrentAsync(
callable $generator,
$statusCode,
ServiceOptions $options
) {
$client = $this->client;
$middlewareStack = $this->createMiddlewareStack($options);
$progress = [];
$sendAsync = function ($request, $options) use ($client, $progress) {
if ($request->getMethod() == 'HEAD') {
$options['decode_content'] = false;
}
$options["progress"] = function(
$downloadTotal,
$downloadedBytes,
$uploadTotal,
$uploadedBytes) use($request, $progress){
// extract blockid from url
$url = $request->getUri()->getQuery();
parse_str($url, $array);
// this array can be written to file or session etc
$progress[$array["blockid"]] = ["download_total" => $downloadTotal, "downloaded_bytes" => $downloadedBytes, "upload_total" => $uploadTotal, "uploaded_bytes" => $uploadedBytes];
};
return $client->sendAsync($request, $options);
};
$handler = $middlewareStack->apply($sendAsync);
$requestOptions = $this->generateRequestOptions($options, $handler);
$promises = \call_user_func(
function () use (
$generator,
$handler,
$requestOptions
) {
while (is_callable($generator) && ($request = $generator())) {
yield \call_user_func($handler, $request, $requestOptions);
}
}
);
$eachPromise = new EachPromise($promises, [
'concurrency' => $options->getNumberOfConcurrency(),
'fulfilled' => function ($response, $index) use ($statusCode) {
//the promise is fulfilled, evaluate the response
self::throwIfError(
$response,
$statusCode
);
},
'rejected' => function ($reason, $index) {
//Still rejected even if the retry logic has been applied.
//Throwing exception.
throw $reason;
}
]);
return $eachPromise->promise();
}
Suppose there is an "addUser" function, inside we need to insert a record to "Account" table and "User" table, so the two steps have to be within a transaction too, so we will write the following code:
function addUser (userName, password) {
sequelize.transaction(function () {
return AccountModel.create(...)
.then(UserModel.create(...))
})
}
However, in another "addTeam" function, inside we need to insert a record to "Team" table and create a admin user using the above function. The function also need to be wrapped inside a transaction.
So the problem comes, the "addUser" function sometimes need to begin a new transaction, and sometimes need to use the transaction passed in. The most obvious ways is below:
function addUser (userName, password, transaction) {
let func = function (t) {
return AccountModel.create(..., t)
.then(()=>UserModel.create(..., t)));
if (transaction) func(t);
else sequelize.transaction(x=>func(t));
}
function addTeam() {
sequelize.transaction(x=> {
TeamModel.create(..., x)
.then(y=>addUser(x));
});
}
Obviously, it is awful. How to deal with it easily, which let transaction totally transparent to the caller like below:
#Transaction
async function addUser(userName, password) {
await AccountModel.create(...);
await UserModel.create(...);
}
#Transaction
async function addTeam(...) {
await TeamModel.create(...);
await addUser(...);
}
sequelize.transaction accepts an options object - If options.transaction is set, this will create a savepoint in the transaction (provided that the SQL dialects supports it), otherwise it will create a new transaction
http://docs.sequelizejs.com/en/latest/api/sequelize/#transactionoptions-promise
So you should be able to do simply
sequelize.transaction({ transaction }, x=>func(t));
If you use CLS, a very simple helper function will do the work.
const cls = require('continuation-local-storage');
const Sequelize = require('sequelize');
const NAMESPACE = 'your-namespace';
// Use CLS for Sequelize
Sequelize.cls = cls.createNamespace(NAMESPACE);
const sequelize = new Sequelize(...);
/* * * * * * * * * * * * * * * * * * * * *
* THE MAGIC: Create a transaction wrapper
* * * * * * * * * * * * * * * * * * * * */
function transaction(task) {
return cls.getNamespace(NAMESPACE).get('transaction') ? task() : sequelize.transaction(task);
};
/* * * * * * * * * * * * * * * * * * * * *
* Your code below
* * * * * * * * * * * * * * * * * * * * */
function addUser(userName, password) {
return transaction(function() {
return AccountModel
.create(...)
.then(() => UserModel.create(...));
});
}
function addTeam() {
return transaction(function() {
return TeamModel
.create(...)
.then(() => addUser(...));
});
}
I solved the problem, and feel great.
I used the CLS feature sequelize provides, like the code below:
let namespace = Sequelize.cls = cls.createNamespace('myschool');
export const db = new Sequelize(config.db.url);
export const trans = option => operation => async function () {
let t = namespace.get('transaction');
let hasTrans = !!t;
t = t || await db.transaction();
try {
let result = await operation.apply(null, arguments);
if (!hasTrans) await t.commit();
return result;
}
catch (e) {
if (!hasTrans) await t.rollback();
throw e;
}
};
The above code just create a transaction and commit it if there is no transaction in the local context, otherwise just leave it.
And each business function want a transaction just need to use the above high order function to wrap like below:
export const createSchool = trans()( async (name, accountProps) => {
let school = await SchoolModel.create({name});
let teacher = await createTeacher({...accountProps, schoolId: school.get('id')});
return {school, teacher};
});
Although there is an accepted answer, below answer refers to a chain of commands (not necessarily nested) we may want to execute in the same transaction, using async-await:
try{
// create a transaction
let transaction = await sequelize.transaction();
// "parent" insertion, id is auto increment, so we will need the id of inserted data
let record = await Parent_Record.create({
data_1: req.params.data1,
data_2: req.params.data2,
....
}, {transaction});
//the array contains data depending upon the id of inserted parent transaction
for ( x in dataForChildTable) {
result = await Child_Record.create({
id: record.id, //same transaction so we can use record.id of parent
.....
other_data: dataForChildTable[x].otherData}, {transaction});
}
//commit the transaction
await transaction.commit();
} catch (err) {
console.log(err.message);
}
You could use zb-sequelize which adds transactional decorators.
import { Transactional, Tx } from 'zb-sequelize';
#Transactional
function addUser(user, password, #Tx transaction) {
// no need to create, commit or rollback a transaction.
}
You do need to initialize it with your sequelize instance.
import { Sequelize } from 'sequelize';
import { initSequelizeResolver } from 'zb-sequelize';
// you already have this somewhere.
const sequelize = new Sequelize(options);
// you need to add this:
initSequelizeResolver((args) => sequelize);
I'm using Cordova to build an Android and iOS application. I'm using google closure.js, node.js, and handlebars as my template render. I'd like to implement internationalization using this guide. My compile process is using grunt. To add a helper in handlebars i'm using
goog.provide('myproject.cat.HandlebarsHelpers'); // {{alias HandlebarsHelpers}}
goog.scope(function () {
// {{strict_and_dependency_aliases}}
/**
* The HandlebarsHelpers module defines and registers
* Handlebars helper functions.
*
* #const
* #see handlebarsjs.com/#helpers
* #public
*/
myproject.cat.HandlebarsHelpers = {};
// {{module_alias}}
/**
* TODO
* #param
* #return {string} - translated string
* #private
*/
function i18nTranslation() {
//return "hi";
// I will show you later how to really load a catalog
// + some more specific cases
var mySpanishCatalog = require('fr'); << - ERROR
var i18n = new Jed(mySpanishCatalog);
var translation = i18n.gettext('Hello world!');
return "Hola";
// arguments = in template params + a handlebars hash
// this = in template context
//var value = Array.prototype.slice.call(
// arguments, 0, arguments.length - 1);
//return i18n.gettext.apply(i18n, value);
}
/**
* Enum which maps helper names to functions.
*
* #enum {!Function}
* #private
*/
var helpers = {
'i18n': i18nTranslation
};
/**
* Initialise.
*
* #public
*/
HandlebarsHelpers.init = function () {
var helperName, helperFunction;
for (helperName in helpers) {
if (helpers.hasOwnProperty(helperName)) {
helperFunction = helpers[helperName];
window.Handlebars.registerHelper(helperName, helperFunction);
}
}
};
});
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.
I have a form build from the kohana framework which should send an email.
when I press the "send" button, everything seems to work fine...no error messages...but no email turns up!
If I can´t see anything in firebug..where can I look?
the application logs state
"error: Missing i18n entry contact.captcha.valid for language en_US"
but I don´t know how to get to the bottom of the problem..any help welcome..
yours,
Rob
Ill try and find out which version im using.....the application is the latest version of Ushahidi (2.2.1) www.ushahidi.com
<?php defined('SYSPATH') OR die('No direct access allowed.');
/**
* Captcha library.
*
* $Id: Captcha.php 3917 2009-01-21 03:06:22Z zombor $
*
* #package Captcha
* #author Kohana Team
* #copyright (c) 2007-2008 Kohana Team
* #license http://kohanaphp.com/license.html
*/
class Captcha_Core {
// Captcha singleton
protected static $instance;
// Style-dependent Captcha driver
protected $driver;
// Config values
public static $config = array
(
'style' => 'basic',
'width' => 150,
'height' => 50,
'complexity' => 4,
'background' => '',
'fontpath' => '',
'fonts' => array(),
'promote' => FALSE,
);
/**
* Singleton instance of Captcha.
*
* #return object
*/
public static function instance()
{
// Create the instance if it does not exist
empty(self::$instance) and new Captcha;
return self::$instance;
}
/**
* Constructs and returns a new Captcha object.
*
* #param string config group name
* #return object
*/
public static function factory($group = NULL)
{
return new Captcha($group);
}
/**
* Constructs a new Captcha object.
*
* #throws Kohana_Exception
* #param string config group name
* #return void
*/
public function __construct($group = NULL)
{
// Create a singleton instance once
empty(self::$instance) and self::$instance = $this;
// No config group name given
if ( ! is_string($group))
{
$group = 'default';
}
// Load and validate config group
if ( ! is_array($config = Kohana::config('captcha.'.$group)))
throw new Kohana_Exception('captcha.undefined_group', $group);
// All captcha config groups inherit default config group
if ($group !== 'default')
{
// Load and validate default config group
if ( ! is_array($default = Kohana::config('captcha.default')))
throw new Kohana_Exception('captcha.undefined_group', 'default');
// Merge config group with default config group
$config += $default;
}
// Assign config values to the object
foreach ($config as $key => $value)
{
if (array_key_exists($key, self::$config))
{
self::$config[$key] = $value;
}
}
// Store the config group name as well, so the drivers can access it
self::$config['group'] = $group;
// If using a background image, check if it exists
if ( ! empty($config['background']))
{
self::$config['background'] = str_replace('\\', '/', realpath($config['background']));
if ( ! is_file(self::$config['background']))
throw new Kohana_Exception('captcha.file_not_found', self::$config['background']);
}
// If using any fonts, check if they exist
if ( ! empty($config['fonts']))
{
self::$config['fontpath'] = str_replace('\\', '/', realpath($config['fontpath'])).'/';
foreach ($config['fonts'] as $font)
{
if ( ! is_file(self::$config['fontpath'].$font))
throw new Kohana_Exception('captcha.file_not_found', self::$config['fontpath'].$font);
}
}
// Set driver name
$driver = 'Captcha_'.ucfirst($config['style']).'_Driver';
// Load the driver
if ( ! Kohana::auto_load($driver))
throw new Kohana_Exception('core.driver_not_found', $config['style'], get_class($this));
// Initialize the driver
$this->driver = new $driver;
// Validate the driver
if ( ! ($this->driver instanceof Captcha_Driver))
throw new Kohana_Exception('core.driver_implements', $config['style'], get_class($this), 'Captcha_Driver');
Kohana::log('debug', 'Captcha Library initialized');
}
/**
* Validates a Captcha response and updates response counter.
*
* #param string captcha response
* #return boolean
*/
public static function valid($response)
{
// Maximum one count per page load
static $counted;
// User has been promoted, always TRUE and don't count anymore
if (self::instance()->promoted())
return TRUE;
// Challenge result
$result = (bool) self::instance()->driver->valid($response);
// Increment response counter
if ($counted !== TRUE)
{
$counted = TRUE;
// Valid response
if ($result === TRUE)
{
self::instance()->valid_count(Session::instance()->get('captcha_valid_count') + 1);
}
// Invalid response
else
{
self::instance()->invalid_count(Session::instance()->get('captcha_invalid_count') + 1);
}
}
return $result;
}
/**
* Gets or sets the number of valid Captcha responses for this session.
*
* #param integer new counter value
* #param boolean trigger invalid counter (for internal use only)
* #return integer counter value
*/
public function valid_count($new_count = NULL, $invalid = FALSE)
{
// Pick the right session to use
$session = ($invalid === TRUE) ? 'captcha_invalid_count' : 'captcha_valid_count';
// Update counter
if ($new_count !== NULL)
{
$new_count = (int) $new_count;
// Reset counter = delete session
if ($new_count < 1)
{
Session::instance()->delete($session);
}
// Set counter to new value
else
{
Session::instance()->set($session, (int) $new_count);
}
// Return new count
return (int) $new_count;
}
// Return current count
return (int) Session::instance()->get($session);
}
/**
* Gets or sets the number of invalid Captcha responses for this session.
*
* #param integer new counter value
* #return integer counter value
*/
public function invalid_count($new_count = NULL)
{
return $this->valid_count($new_count, TRUE);
}
/**
* Resets the Captcha response counters and removes the count sessions.
*
* #return void
*/
public function reset_count()
{
$this->valid_count(0);
$this->valid_count(0, TRUE);
}
/**
* Checks whether user has been promoted after having given enough valid responses.
*
* #param integer valid response count threshold
* #return boolean
*/
public function promoted($threshold = NULL)
{
// Promotion has been disabled
if (self::$config['promote'] === FALSE)
return FALSE;
// Use the config threshold
if ($threshold === NULL)
{
$threshold = self::$config['promote'];
}
// Compare the valid response count to the threshold
return ($this->valid_count() >= $threshold);
}
/**
* Returns or outputs the Captcha challenge.
*
* #param boolean TRUE to output html, e.g. <img src="#" />
* #return mixed html string or void
*/
public function render($html = TRUE)
{
return $this->driver->render($html);
}
/**
* Magically outputs the Captcha challenge.
*
* #return mixed
*/
public function __toString()
{
return $this->render();
}
} // End Captcha Class
From the little details provided I think your site uses the I18n library from Kohana for internationalization of the site. The other option is your site use Kohana messages for showing form errors.
The Validation class uses the __() function internally when generation validation errors. I think you don't have a message for key valid specified in APPPATH/messages/contact/captcha.php.
You should try to investigate more on how the form is processed and whether some validation errors are being generated. There might be an error in the captcha validation and despite it seem there is no error, there might be and just the error message could not be shown.
class Contact_Controller extends Main_Controller
{
function __construct()
{
parent::__construct();
}
public function index()
{
$this->template->header->this_page = 'contact';
$this->template->content = new View('contact');
$this->template->header->page_title .= Kohana::lang('ui_main.contact').Kohana::config('settings.title_delimiter');
// Setup and initialize form field names
$form = array (
'contact_name' => '',
'contact_email' => '',
'contact_phone' => '',
'contact_subject' => '',
'contact_message' => '',
'captcha' => ''
);
// Copy the form as errors, so the errors will be stored with keys
// corresponding to the form field names
$captcha = Captcha::factory();
$errors = $form;
$form_error = FALSE;
$form_sent = FALSE;
// Check, has the form been submitted, if so, setup validation
if ($_POST)
{
// Instantiate Validation, use $post, so we don't overwrite $_POST fields with our own things
$post = Validation::factory($_POST);
// Add some filters
$post->pre_filter('trim', TRUE);
// Add some rules, the input field, followed by a list of checks, carried out in order
$post->add_rules('contact_name', 'required', 'length[3,100]');
$post->add_rules('contact_email', 'required','email', 'length[4,100]');
$post->add_rules('contact_subject', 'required', 'length[3,100]');
$post->add_rules('contact_message', 'required');
$post->add_rules('captcha', 'required', 'Captcha::valid');
// Test to see if things passed the rule checks
if ($post->validate())
{
// Yes! everything is valid - Send email
$site_email = Kohana::config('settings.site_email');
$message = Kohana::lang('ui_admin.sender').": " . $post->contact_name . "\n";
$message .= Kohana::lang('ui_admin.email').": " . $post->contact_email . "\n";
$message .= Kohana::lang('ui_admin.phone').": " . $post->contact_phone . "\n\n";
$message .= Kohana::lang('ui_admin.message').": \n" . $post->contact_message . "\n\n\n";
$message .= "~~~~~~~~~~~~~~~~~~~~~~\n";
$message .= Kohana::lang('ui_admin.sent_from_website'). url::base();
// Send Admin Message
email::send( $site_email, $post->contact_email, $post->contact_subject, $message, FALSE );
$form_sent = TRUE;
}
// No! We have validation errors, we need to show the form again, with the errors
else
{
// repopulate the form fields
$form = arr::overwrite($form, $post->as_array());
// populate the error fields, if any
$errors = arr::overwrite($errors, $post->errors('contact'));
$form_error = TRUE;
}
}
$this->template->content->form = $form;
$this->template->content->errors = $errors;
$this->template->content->form_error = $form_error;
$this->template->content->form_sent = $form_sent;
$this->template->content->captcha = $captcha;
// Rebuild Header Block
$this->template->header->header_block = $this->themes->header_block();
$this->template->footer->footer_block = $this->themes->footer_block();
}
}
My problem has been solved!
My Email server was not set up properly. I resolved the postfix issue and now the comments form is working as it should,
thanks to all who answered,
Rob