I'm having trouble understanding how to handle phoenix views properly.
Let's say that a client is calling "login_user" api.
My controller will handle the request, checking if user is enabled to access or not. As response, i need to send user's data to the caller.
Now, after controller's login logic, what's the proper way to respond?
1) fetch all data needed by the caller inside the controller:
user_data = function_that_fetch_user_data()
conn
|> put_resp_header("content-type", "application/json")
|> put_status(:ok)
|> send_resp(Status.code(:ok), user_data)
2) render the view
conn
|> put_resp_header("content-type", "application/json")
|> put_status(:ok)
|> render(login.json, user)
and fetch data inside it
defmodule CryptomonitorWeb.UserView do
use MyAppWeb, :view
def render("login.json", user) do
token = generate_user_token(user.mail)
%{
token: token,
email: user.mail,
group: "operators"
}
end
end
Getting data and building my response directly in the controller is more clear and clean to me, but i want to know what's the "right" way to handle api responses. My app will usually just return json to the client and sometimes render some html page.
Views are representation of a data you want to send to as a response. So first you need to have some data, second you have to present it in the way it have to be presented in some API or an HTML page.
You are definitely don't want to go into a database from a view, but if you don't go there, then the border is more subtle.
Still, generate_user_token looks like a data emitter, not like a data presenter. And who knows, maybe one day someone will change the function, so it'll hit a database to generate token.
To avoid related troubles, better to have data generators out of a view. Place them into controller, then send results to a view.
So, I'm voting for option 1.
Related
I have the end points "/customers" and "/api/v1/customers", in html and json respectively for a list of customers. Do I have to create 2 different controllers and thus actions for them? Or can I return html or json from a single controller and action depending a requested format: html or json? Note that for "/api/v1/customers" I need authentication via an Api Key.
You can have one controller and action for both endpoints, but I would advise against it.
You mentioned that those controllers need to do different stuff, so instead of adding stuff like "if json then check api key" make two separate controllers and extract common code of getting all the customers.
There is a great talk about untangling business logic from http interface: http://www.elixirconf.eu/elixirconf2016/lance-halvorsen Getting a list of customers might be out of your controllers, so at the end you will have two controllers like this:
defmodule MyApp.Api.CustomersController do
plug MaApp.ApiAuth #plug for checking api key
def index(conn, params) do
...
customers = ActualLogic.get_customers()
...
end
end
def MyApp.CustomersController do
plug MyApp.UserAuth #for example checks if user is logged in
def index(conn, params) do
...
customers = ActualLogic.get_customers()
...
end
end
At the end your controller does not perform any logic, it calls something else to do the job and action is responsible only for web stuff like parsing params, authentication via api key, session cookies and translating end result to json/html.
I'm making a project in pyramid framework, so i have a view which have a form in it with 2 input type texts and a submit button.
The form is a post method, so im getting them with a POST request.
I want to send them to a new view and display them on the screen.
meaning:
on 0.0.0.0:6543 is the form on first view.
I want to display the values the user insert in the input on 0.0.0.0:6543/here
I tried with HTTPfound but i guess im missing an understanding on how to really pass the variables.
Please help me...
The easiest way to accomplish is to use sessions.
You need a session backend which stores your data on a server (see pyramid_redis_session). There are also cookie-based session solutions where all data is stored on the client side.
The first view writes all passed over data to a session:
request.session["mydata"] = value
The second view reads data from the session
print(request.session["mydata"])
Another way to pass the data from one view to another is via the URL. This does not require server-side support, unlike sessions. Also, it's RESTful ;)
return HTTPFound('/here?greeting=Hello&subject=World')
In your second view you then simply get the variables from request.GET:
greeting = request.GET.get('greeting', '')
subject = request.GET.get('subject', '')
# pass the data to the template
return {
"greeting": greeting,
"subject": subject
}
Regarding your comment: You can't use HTTPFound with POST. You can, however, directly submit your form to /here using <form method="post" action="/here" ...>. In this case you'll be able to access the data using request.POST.get('greeting').
I've been working with Sails since couple of weeks ago, I came from Rails and I don't have any experience working with Node.js.
Now I'm trying to make a robust token authentication using jsonwebtoken.
https://github.com/auth0/node-jsonwebtoken
I followed this guide http://thesabbir.com/how-to-use-json-web-token-authentication-with-sails-js/ and everything worked fine.
I'm able to make a sign up, sign in and then use the token correctly for different actions.
Now, there are some actions where I'd like to use the login user,
something like devise current_user helper.
For example, when creating a comment, this comment should belongs to the current user.
Using Sabbir Ahmed guide, in the line 33 from the isAuthorized.js policy the token gets decrypted so I can get the current user id from there.
So, my question is, what should be the best way to get the current user and be able to use it later in some controller?
For example I tried something like:
# isAuthorized.js line 34, after getting decrypted token
User.findOne({id: token.id}).exec(function findOneCB(err, found){
currentUser = found;
});
But, on this way, because this is an async action I can't use this currentUser in a controller.
I want to store the current user in order to be able to use it later in some controller without repeated the same code in each controller, something like a helper or maybe a service.
The trick is where you place the next(). Since you are making an async call, the control should only be transferred to next policy/ controller once the database action is competed.
You should modify the policy to:
User.findOne({id: token.id}).exec(function findOneCB(err, found){
if(err) next(err);
req.currentUser = found;
next();
});
And you should be able to access the user details in controllers that use isAuthorized policy via req.currentUser
If by
For example, when creating a comment, this comment should belongs to the current user.
what you mean is certain attributes like username, and country etc, rather than querying the database after verification, what you can choose to do is to send these additional attributes to jwToken.issue in api/controllers/UsersController.js
eg.
jwToken.issue({
id: user.id,
username: user.name,
country: user.country
})
How that helps is, you can keep api/policies/isAuthorized.js as is, and in all the controllers that you use in the future, you can access the payload values from as
token.username or token.country
Instead of having to query the database again, thereby saving you valuable response time.
Beware however, of the data you choose to send in the token (you could also send {user:user} if you want to) however, as the secret key or hashing is not required to decrypt the payload as you can figure # jwt.io , you might want to exercise restraint.
I have a few questions that I couldn't find answers anywhere online.
Does sails.js framework support HTTP PATCH method? If not - does anyone know if there is a planned feature in the future?
By default if I create method in a controller it is accessible with GET request is it the routes.js file where I need to specify that method is accessible only via POST or other type of methods?
How would you create a policy that would allow to change protected fields on entity only for specific rights having users. I.e: user that created entity can change "name", "description" fields but would not be able to change "comments" array unless user is ADMIN?
How would you add a custom header to "find" method which specifies how many items there are in database? I.e.: I have /api/posts/ and I do query for finding specific items {skip: 20; limit: 20} I would like to get response with those items and total count of items that would match query without SKIP and LIMIT modifiers. One thing that comes to my mind is that a policy that adds that that custom header would be a good choice but maybe there is a better one.
Is there any way to write a middle-ware that would be executed just before sending response to the client. I.e.: I just want to filter output JSON not to containt some values or add my own without touching the controller method.
Thank you in advance
I can help with 2 and 5. In my own experience, here is what I have done:
2) I usually just check req.method in the controller. If it's not a method I want to support, I respond with a 404 page. For example:
module.exports = {
myAction: function(req, res){
if (req.method != 'POST')
return res.notFound();
// Desired controller action logic here
}
}
5) I create services in api/services when I want to do this. You define functions in a service that accept callbacks as arguments so that you can then send your response from the controller after the service function finishes executing. You can access any service by the name of the file. For example, if I had MyService.js in api/services, and I needed it to work with the request body, I would add a function to it like this:
exports.myServiceFunction = function(requestBody, callback){
// Work with the request body and data access here to create
// data to give back to the controller
callback(data);
};
Then, I can use this service from the controller like so:
module.exports = {
myAction: function(req, res){
MyService.myServiceFunction(req.body, function(data){
res.json(data);
});
}
}
In your case, the data that the service sends back to the controller through the callback would be the filtered JSON.
I'm sorry I can't answer your other questions, but I hope this helps a bit. I'm still new to Sails.js and am constantly learning new things, so others might have better suggestions. Still, I hope I have answered two of your questions.
Let's say I have two controller methods: Users.preInsert and Users.insert. The preInsert method is the one used to display the user entry form (GET), while the insert method is responsible for the actual insertion (POST) or calling the 'insert' service.
This is how the routes looks like:
GET /users/add controllers.Users.preInsert(...)
POST /users/add controllers.Users.insert(...)
So how do I redirect a request (POST to GET) without losing the parameters like error messages returned from the insert service and the values inputed by the client so that they can be accessed and displayed in the entry form. The parameters may involve some complex objects. I have implemented it using the Caching API but I would like to know if there are any better ways of doing it.
That's the exact purpose of the Form objects (http://www.playframework.com/documentation/2.1.1/ScalaForms).
And I think there is a an error in your routes, it could look like:
GET /users/add controllers.Users.preInsert(...)
POST /users/add controllers.Users.insert(...)
You should definitively take a look at the form sample.
You don't need to redirect it back to the preInsert action, instead at the beginning of the insert check if form has errors and it it has display your view containing form (the same which you used in preInsert). It's described in the doc mentioned by nico_ekito in section Handling binding failure