dwyl / abase

:house: A (solid) Base for your Web Application.
9 stars 0 forks source link

Propose and API to steer our development #19

Open jrans opened 7 years ago

jrans commented 7 years ago

Right now we believe that a joi object will be a good start to being able to define our views and database model.

However without designing our api, we will not know the full limitations in how we choose to conifgure our forms and data.

Create propositions and review together.

Update: Outcome of Ideation

Step 1 Step 2 Step 3
#20
JSON Custom Object Permissions
Validation with joi
html + endpoint
db + relations
Initialises our Object Our Own Composable Api for Us Interpreted from our Object
can be added on with api Allows a user to modify
inspired by joi
Suitable for user with no js knowledge better for experienced developer

Will skip out step 2 for now to figure out details and inspire our api design.

eliasmalik commented 7 years ago

Here's my sketch:

'use strict'

var Joi = require('joi')
var db = require('db')
var utils = require('utils')

// Define the user model with a schema
// .createModel wraps/extends the Joi object with methods we need for e.g. permissions
var model = utils.createModel({
  id: Joi.number().integer().required(), // required refers to DB field
  username: Joi.string().min(3).max(20).required(),
  password: Joi.string().min(10).required(),
  email: Joi.string().email(),
  dob: Joi.date(),
  field1: Joi.number(),
  field2: Joi.string(),
})

// define permissions
// all fields are editable by the users who they belong to by default
// if no read/write/delete methods are called, default is full access
// atm no role hierarchy so all permitted roles need to be explicitly specified
model.read('user', 'admin').write('admin') // this is top level permissions on whole user object
model.username.read('user', 'admin').write('admin').delete('admin') // granular permissions on fields
model.id.freeze() // not editable by anyone, readable by anyone
model.dob.write(3, 'user') // user can edit at most 3 times before frozen/reset (is this useful?)

// import the full model into the DB
// migrate method knows about the mapping b/w Joi schema and DB types
db.migrate(model)

/*
And in the route definition:
 */
[{
  method: 'POST',
  path: '/login',
  handler: (req, res) => {
    db.checkUser(req.payload.username, req.payload.password, (err, isValid, token, msg) => {
      if (err || !isValid)
        return reply(msg);

      reply.redirect('/').state('token', token)
    })
  },
  config: {
    validate: {
      payload: model.joi('username', 'password') // specify which fields are requred on this endpoint
    }
  }
}, {
  method: 'GET',
  path: '/login',
  handler: (req, res) => {
    var model = server.app.model

    // define a subset from which to render html
    // render method knows about the mapping b/w schema and HTML
    // should be passed the user role so it knows which fields it can parse
    var loginSchema = utils.pick(model, ['username', 'password'], req.userRole)
    var loginHtml = utils.renderForm(loginSchema)
  },
  config: {
    auth: false
  }
}, {
  method: 'GET',
  path: '/dashboard',
  handler: (req, res) => {
    var model = server.app.model

    // define a subset from which to render html
    // render method knows about the mapping b/w schema and HTML
    // should be passed the user role so it knows which fields it can parse
    var adminSchema = utils.pick(model, ['username', 'email', 'field1'], req.userRole)
    var adminHtml = utils.renderData(db, adminSchema)
  },
}]
samhstn commented 7 years ago

server.js file

server.register([{register: Abase, {ops: {file: './schema.js', }}], (err) => {
  if (err)
    throw err;

  server.route({
    method: 'GET',
    path: '/login',
    handler: (request, reply) => {
      Abase.login(request.payload.login, request.payload.password);
    }
  });

  server.route({
    method: 'GET',
    path: '/registration',
    handler: (request, reply) => {
      Abase.registration(request.payload.email, request.payload.country);
    }
  });
});

schema.js file

module.exports = {
  "Login": {
    "username": Joi2.string().isRequired(),
    "password|pass...": Joi2.string().includesNumber().isRequired()
  },
  "Registration": {
    "Required": {
      "email": Joi2.email(),
      "country": Joi2.string()
    },
    "Optional": {
      "profile": Joi2.textBox(),
      "company": Joi2.string(),
    }
  }
};
jrans commented 7 years ago

Not exact in how the entire config is formed but stressing how the config is separated.

var abase = require('abase')l

var user = Abase({
  username: { validation: Joi.string().alphanum().min(3).max(30) },
  password: {
    validation: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/)
    permissions: {
      // write: ['user'] by default
      read: [] // everyone by default
    }
  }
  birthyear: {
    validation: Joi.number().integer().min(1900).max(2013)
  },
  email: {
    validation: Joi.string().email()
    permissions: {
      read: ['user', 'admin']
    }
  }
});

var login = user({
  type: 'write',
  fields: [
    { type: 'username', options: { required: true } },
    { type: 'password' }
  ]
});

var register = user({
  type: 'write',
  url: 'sign/me/up'
  fields: [
    { type: 'username' },
    { type: 'birthyear' }
    { type: 'email' },
    { type: 'email' },
    { type: 'password' },
    { type: 'password' }
  ]
});

var details = user({
  type: 'read',
  fields: [
    { type: 'username' },
    { type: 'birthyear' }
    { type: 'email' }
  ]
});
SimonLab commented 7 years ago

Create a Hapi plugin:

When the plugin is registered with the Hapi server we can add also an option object which will be a json config file where all the fields with their type are defined. This json config file should be easy to write for the creator of the applications:

1) create the json config object (need to define how to create a simple structure and rules for the json)

config.json

{
  email: {
    type: "string",
  },
  password: {
    type: "password"
  }
}

now on the application we can register the plugin with the config:

server.register([{ register: require('abase'), options:require('config.json') }],
 function (err) {
...
});

By registering the plugin the server will have now some new endpoints (need to precise maybe wireframe all the endpoints, and define post and get method):

/signup
/login
/user/{idUser} //show info of a user
/edit/user/{idUser} //show the page where the user can edit her details
...

These endpoints automatically render some html based on the config.json. problem: How to customise these pages (add a logo of the company, display footer...)

When a user send a post request for example when a login page is send, all the payload will be validate with Joi. For that we are generating a Joi object for each page based on the config.json

The first step to start migth be to create a simple basic registration page

-> the config.json just have email and password -> the GET /signup endpoint is added to the server and it display a simple html form -> The POST /signup endpoint is created and it sends the payload data when a user register. The handler of the endpoint will validate with Joi the fields (based on config.js) -> If the validation pass the handler will generete some sql and execute the database function to save the data of the user