sending email from form - protocol String.Chars not implemented - phoenix-framework

I have a simple contact form sending email and message fields. I'm using Mailgun.
I can send an email perfectly from the console, but when I try and send from the form in the UI, I receive the following error:
protocol String.Chars not implemented for %Iotc.Contact.Email{__meta__:
#Ecto.Schema.Metadata<:loaded, "contact_emails">, email:
"cooper.sf#gmail.com", id: 25, inserted_at: ~N[2017-06-15 10:34:26.229957],
message: "I'd like a cake please.", updated_at: ~N[2017-06-15 10:34:26.229977]}
email_controller.ex
def create(conn, %{"email" => email_params,
"email" => %{"email" => email,
"message" => message}}) do
changeset = Contact.Email.changeset(%Iotc.Contact.Email{}, email_params)
case Contact.create_email(email_params) do
{:ok, email} ->
Iotc.Mailer.send_email(email, message) #<this is the issue.
conn
|> put_flash(:info, "Email sent")
|> redirect(to: email_path(conn, :index))
{:error, changeset} ->
conn
|> put_flash(:error, "Something went wrong")
|> render("index.html", changeset: changeset)
end
I've tried changing
Iotc.Mailer.send_email(email, message)
to
Iotc.Mailer.send_email(email_params)
But I then get
key :to not found in: %{"email" => "cooper.sf#gmail.com",
"message" => "I'd like a cake please.", "name" => "Simon"}

The pattern {:ok, email} shadows the outer email string value with a %Iotc.Contact.Email{} struct while send_email expects the first argument to be just the email address string value. You can get the address value by doing email.email:
case Contact.create_email(email_params) do
{:ok, email} ->
Iotc.Mailer.send_email(email.email, message)
...
...
end
or you can change the pattern to ignore the returned struct since you're not using it anyways:
case Contact.create_email(email_params) do
{:ok, _} ->
Iotc.Mailer.send_email(email, message)
...
...
end

Related

Authorization for subscriptions Graphql (Absinthe) with token

I'm trying to add authentication to this subscription test, since I keep getting Not Authorized when I run mix test.
I've seen that you need to add a login mutation to the push_doc function but I was wondering. Is there any way to use only the token to authenticate, just like regular mutations that use :
conn =
build_conn()
|> put_req_header("authorization", #token)
|> get("/api", query: #query)
assert json_response(conn, 200) == %{
"data" => %{
"authors" => [%{"name" => "Jennifer"}]
}
}
This is my current subscription test:
test "1. Subscribe to createAuthor", %{socket: socket} do
# setup a subscription
ref = push_doc(socket, #subscription)
assert_reply(ref, :ok, %{subscriptionId: subscription_id})
# run a mutation to trigger the subscription
ref = push_doc(socket, #mutation)
assert_reply(ref, :ok, reply)
assert %{
data: %{
"createAuthor" => %{
"name" => "Jennifer"
}
}
} = reply
# check to see if we got subscription data
expected = %{
result: %{
data: %{
"createAuthor" => %{
"name" => "Jennifer"
}
}
},
subscriptionId: subscription_id
}
assert_push("subscription:data", push)
assert expected == push
end
My general question is. Can I only pass the token (which I have hardcoded) into a function to authenticate for subscriptions?
I managed to do the test as follows, making the mutation using put_req_header()
test "1. Subscribe to createAuthor", %{socket: socket} do
# setup a subscription
ref = push_doc(socket, #subscription)
assert_reply(ref, :ok, %{subscriptionId: subscription_id})
# run a mutation to trigger the subscription
conn =
post(
build_conn()
|> put_req_header("authorization", #token),
"/api",
query: #mutation
)
assert json_response(conn, 200) == %{
"data" => %{
"createAuthor" => %{
"name" => "Jennifer"
}
}
}
# check to see if we got subscription data
expected = %{
result: %{
data: %{
"createAuthor" => %{
"name" => "Jennifer"
}
}
},
subscriptionId: subscription_id
}
assert_push("subscription:data", push)
assert expected == push
end
You can make a token with an expiry in like 100 years, signed with your key. Hardcode that and it's good forever as long as you use the same SK.
Are you adding "Bearer" in front of your token?

Sending a patch request to edit video using vimeo api not working

I am trying to send a patch request to edit a video using vimeo api using ruby. The request is successful but vimeo is not able to read my the payload(the title and description), that is, the the title and the description is not getting changed. I have used HTTParty, RestClient as well as Net::HTTP but none of it works. Below are my code that i have implemented to send a patch request,
RestClient
payload = {"description" => "Test Description", "name" => "Test Video"}
res = RestClient.patch(
vimeo_edit_url,
payload.to_s,
{ "Authorization" => auth })
NET::HTTP
options = {'description' => "Test Description", 'name' => "Test Video"}
edit_req = Net::HTTP::Patch.new(vimeo_edit_url, initheader = { "Authorization" => auth})
edit_req.data = options.to_s
edit_uri = URI(vimeo_edit_url)
edit_http = Net::HTTP.new(edit_uri.host, edit_uri.port)
edit_http.use_ssl = true
edit_http.verify_mode = OpenSSL::SSL::VERIFY_PEER
edit_response = edit_http.request(edit_req)
Usually the response will be a 200 OK with the updated video details but I get a 200 OK with video details(title and description not being changed). It is as if like vimeo is not able to read my payload.
You probably want to be passing payload, not payload.to_s.
That .to_s is turning your nicely arranged hash into a weird rubyified string, and all of the HTTP libraries you mentioned will accept a hash and handle the conversion for you.
Here's what some different representations look like in an irb session:
>> payload = {"description" => "Test Description", "name" => "Test Video"}
>> payload
=> {"description"=>"Test Description", "name"=>"Test Video"}
>> payload.to_s
=> "{"description"=>"Test Description", "name"=>"Test Video"}"
>> require 'json'
=> true
>> payload.to_json
=> "{"description":"Test Description","name":"Test Video"}"

Mandrill template merge fail

I'm using mandrill to send emails from my API (in ruby with the mandrill-api gem), like signup confirmation email.
I have a merge tag in the template to put the username :
Hello *|USERNAME|*,
Thank you very much for installing the app! ...
...
My ruby code looks like that :
m = Mandrill::API.new ENV['MANDRILL_KEY']
template_name = "app-registration-welcome-email"
template_content = [{}]
message = {
:from_name=> "From Name",
:to=>[
{
:email => user.email,
:name => user.name,
}
],
:global_merge_vars => [{
:name => "username",
:content => user.name
}],
:merge_language => "mailchimp",
:merge => true,
:merge_vars => [{
:rcpt => user.email,
:vars => [{
:name => "username",
:content => user.name
}],
}],
:track_opens => true,
}
m.messages.send_template template_name, template_content, message
Unfortunately, when i receive the email, all is fine (to, name, from, ...) but the merge tag isn't replaced in the body and i still have *|USERNAME|* displayed.
What am I missing here?
You should only need to set USERNAME once, either in :global_merge_vars or :merge_vars.
If you're sending to just one recipient, it doesn't matter which you do.
If there are multiple recipients, use :merge_vars.
Try building message and then puts message.to_json and drop it into the debugger at https://mandrillapp.com/api/docs/messages.JSON.html#method=send-template (Click the 'Try it' button.) See if that gives you any clues.
You might also try using "USERNAME" instead of "username" as your variable name. The docs say merge vars are case-insensitive, but it's worth removing one more possible mismatch.

Play Validation - Custom form field validation with specific field error

case class Address(
address1: String,
city: String,
state: String,
postal: String,
country: String
)
Form(
mapping = mapping(
"address1" -> nonEmptyText,
"city" -> nonEmptyText,
"state" -> nonEmptyText,
"postal" -> nonEmptyText,
"country" -> nonEmptyText
)(Address.apply)(Address.unapply).verifying("Invalid Postal Code!", validatePostal _)
)
def validatePostal(address: Address): Boolean = {
address.country match {
case "US" | "CA" =>
val regex: Regex = ("^(\\d{5}-\\d{4}|\\d{5}|\\d{9})$|^([a-zA-Z]\\d[a-zA-Z]( )?\\d[a-zA-Z]\\d)$").r
regex.pattern.matcher(address.postal).matches()
case _ => false
}
}
Above form validation for postal code works fine with global error displayed on form for invalid US or Canadian postal codes.
I would like to show the error as field error right next to the field than as global error which in my case is shown on top of the form.
Is there a way to use inbuilt Form constraints or verification methods to achieve this instead of FormError's?
You can add the constraint to the field. Then update validatePostal to accept a tuple of the two values instead.
Form(
mapping = mapping(
"address1" -> nonEmptyText,
"city" -> nonEmptyText,
"state" -> nonEmptyText,
"postal" -> tuple(
"code" -> nonEmptyText,
"country" -> nonEmptyText
).verifying("Invalid Postal Code!", validatePostal _),
)((address1, city, state, postal) => Address(address1, city, state, postal._1, postal._2))((address: Address) => Some((address.address1, address.city, address.state, (address.postal, address.country))))
)
Template:
#inputText(
addressForm("postal.code"),
'_label -> "Postal code",
'_help -> "Please enter a valid postal code.",
'_error -> addressForm.error("postal")
)
Defining the error like that you are creating a FormError("","Invalid Postal Code!") object in a form, as it doesn't have key (the first parameter), the framework don't attach the error to the form element.
On form error when you are binding the request to the form, you will have to create a new form removing the FormError("","Invalid Postal Code!") and replacing it with an error FormError("form.id","message")
In our project we created an implicit def for Form to replace the form errors (we couldn't find a way to create dynamic constraint validations) these are the 2 definitions we have:
def replaceError(key: String, newError: FormError): Form[T] = {
val updatedFormErrors = form.errors.flatMap { fe =>
if (fe.key == key) {
if (form.error(newError.key).isDefined) None
else {
if (newError.args.isEmpty ) Some(FormError(newError.key,newError.message,fe.args))
else Some(newError)
}
} else {
Some(fe)
}
}
form.copy(errors = updatedFormErrors.foldLeft(Seq[FormError]()) { (z, fe) =>
if (z.groupBy(_.key).contains(fe.key)) z else z :+ fe
})
}
def replaceError(key: String, message: String, newError: FormError): Form[T] = {
def matchingError(e: FormError) = e.key == key && e.message == message
val oldError = form.errors.find(matchingError)
if (oldError.isDefined) {
val error = if (newError.args.isEmpty) FormError(newError.key,newError.message,oldError.get.args) else newError
form.copy(errors = form.errors.filterNot(e => e.key == key && e.message == message)).withError(error)
}
else form
}
We have those in a class called FormCryptBind (because we also improve the form object with some crypto stuff) and we define the implicit def like this:
implicit def formBinding[T](form: Form[T])(implicit request: Request[_]) = new FormCryptBind[T](form)
We do it like that because just importing the object having this implicit definition, you can use all the FormCryptBind definitions as they were Form's
And we use it like this
import whatever.FormImprovements._
...
object SomeController extends Controller{
...
def submit = Action{ implicit request =>
form.bindRequest.fold(
formWithErrors => {
val newForm = formWithErrors.replaceError("", "formField.required", FormError("formField", "error.required")
BadRequest(someView(newForm)
},
formDetails => Redirect(anotherView(formDetails))
}
As I can't put actual live code from the app, I touched it a little bit :D so expect compile errors if you copy & paste

Undefined method 'dateTime' for Google::ApiClient

Im working on a sinatra based application, in which I get events from a google calendar and display all events as a list. However I have encountered an unusual error when I tried to get the start and end dates of All day events.
Since all day events have an object of type Date, and timed events have an object of type dateTime , these two objects won't get displayed, the error I get is:
no such method named dateTime
It works fine when there is only timed events (dateTime object) events but not when there is an all day event (date object).
Any help would be great.
Code:
require 'rubygems'
require 'google/api_client'
require 'date'
# modified from 1m
# Update these to match your own apps credentials
service_account_email = "" # Email of service account
key_file = "" # File containing your private key
key_secret = 'notasecret' # Password to unlock private key
# Get the Google API client
client = Google::APIClient.new(:application_name => 'GCalendar',
:application_version => '1.0.0')
# Load your credentials for the service account
key = Google::APIClient::KeyUtils.load_from_pkcs12(key_file, key_secret)
client.authorization = Signet::OAuth2::Client.new(
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => 'https://www.googleapis.com/auth/calendar',
:issuer => service_account_email,
:signing_key => key)
motd = Array.new
summary = ""
description = ""
tickerEvent = " "
# Start the scheduler
# Request a token for our service account
client.authorization.fetch_access_token!
# Get the calendar API
calendar = client.discovered_api('calendar','v3')
today = DateTime.now().to_s
# Execute the query
result = client.execute(:api_method => calendar.events.list,
:parameters => {'calendarId' => 'idNo',
'timeMin' => today,
'singleEvents' => true,
'orderBy' => 'startTime'})
events = result.data.items
events.each do |e|
if(DateTime.now() >= e.start.dateTime)
summary = e.summary
description = e.description
tickerEvent = summary.to_s + " - " +description.to_s
motd.push(tickerEvent)
elsif(DateTime.now() >= e.end.dateTime)
motd.delete(e)
end
end
motd.clear
end
Is there a way to check whether the event is a Date type or a DateTime type?
In the google api the start dateTime and end dateTime looks like (This is a timed event):
"start": {
"dateTime": "2013-12-03T12:30:00+13:00",
"timeZone": "Pacific/Auckland"
},
"end": {
"dateTime": "2013-12-03T13:00:00+13:00",
"timeZone": "Pacific/Auckland"
},
whereas an all day event looks like:
"start": {
"date": "2014-01-17"
},
"end": {
"date": "2014-01-18"
},
they are of different types, this is what is causing the error.
Cheers
The method is event.start.date_time not event.start.dateTime.
For events that do not have a time associated with them, event.start.date_time will return nil. You can then call event.start.date, which should return just the date.

Resources