[Go Revel] メール送信の実装方法

まずメール送信用のルートを作成

GET		/mail				  App.Mail

app.go
– net/smtpをimport
– パスワードはアプリ固有のパスワード

import (
	"github.com/revel/revel"
	"net/smtp"
)
func (c App) Mail() revel.Result {

	name := "hpscript"
	email := "test@hpscript.com"
	comments := "桜木町の夜景は良いですね"

	auth := smtp.PlainAuth (
		"",
		"myaddress@gmail.com", // gmailのアドレス
		"hogehoge", // アプリ固有のパスワード
		"smtp.gmail.com",
	)

	msg := []byte("From: info@hpscript.com\r\n" +
		"To: info@hpscript.com\r\n" +
		"Subject: SMTPテスト\r\n" +
		"\r\n" +
		"名前: " + name + "\r\n" +
		"メールアドレス" + email + "\r\n" +
		"問い合わせ内容: " + comments + "\r\n")

	err := smtp.SendMail(
		"smtp.gmail.com:587",
		auth,
		"info@hpscrpt.com",
		[]string{"fugafuga@hotmail.com"}, // 宛先
		msg,
	)

	if err != nil {
		fmt.Println(err)
	}
	return c.Render()
}

これmailtrapでも送れそうだな

[Go Revel] MySQLのuniqueなvalidationでredirect

ユーザのsigninの際に、既に同じユーザ名の登録があれば、登録できないようにしたい。

mysql側ではnameにUnique keyをつけている

mysql> describe users;
+———-+————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+———-+————–+——+—–+———+—————-+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | UNI | NULL | |
| email | varchar(255) | YES | | NULL | |
| password | varchar(255) | YES | | NULL | |
| filepath | varchar(255) | YES | | NULL | |
| message | text | YES | | NULL | |
+———-+————–+——+—–+———+—————-+
6 rows in set (0.01 sec)

これだと、実際に同じnameで入力があった場合、mysql側にはinsertされないが、
ページがそのまま遷移してしまう。
そのため、同じnameの入力があった場合は、入力画面に戻るようにしたい

revelでc.Validation.${} でunique判定が見当たらないので、
実際にselectして、処理する

	result := []models.Users{}
	DB.Where("name = ?", name).First(&result)


	if len(result) != 0 {
		c.Flash.Error("Same username is registered!")
		c.Validation.Keep()
		c.FlashParams()
		return c.Redirect(App.Signin)
	}
	
	hashPassword := getMD5Hash(password)
	DB.Create(&models.Users{
		Name: name,
		Email: email,
		Password: hashPassword,
	})
	
	return c.Render()

これ2時間ぐらいかかった…
go revelとか、documentなさすぎやろ😖😖😖😖

[Go Revel] Templateで else ifを使う

html
– uploadファイルがあればuploadファイル、元からセットされてらファイルがあればその画像、なければplaceholdeの画像を表示する

              {{if .filepath}}
               <img src="{{ .filepath }}" width="150px" height="150px" class="img-icon preview1">
              {{else if .set_filepath}}
              <img src="{{ .set_filepath }}" width="150px" height="150px" class="img-icon preview1">
              {{else}}
              <img src="http://placehold.jp/150x150.png" class="img-icon">
              {{end}}

普通にif, else ifで実装できますね。
controllerがそこそこ複雑になってきた。
djangoもそうだが、controllerを1ファイルでまとめると、縦に長くなるので、controllerを分けたいという欲求が少なからずある

[Go Revel] Templateで値がnilか否かで条件分岐

画像uploadの確認画面で、uploadファイルがあればその画像を表示し、なければplaceholdの画像を表示したい

app.go
– filepathを””で宣言し、uploadファイルがあれば、保存するファイルパスに置き換える

                filepath := ""
	if file1 != nil {
		fmt.Println(c.Params.Files["file1"][0].Filename)
		filename := c.Params.Files["file1"][0].Filename
		data, e := ioutil.ReadAll(file1)
		if e != nil {
			fmt.Println("error")
		}
		ioutil.WriteFile("public/tmp/" + filename, data, 777)
		filepath = "public/tmp/" + filename
	}

uploadconfirm.html
– if eq で値の中身を確認して条件分岐で表示する。 {{if .filepath == “” }}と書くとエラーになるので注意が必要

            <td>
              {{if eq .filepath ""}}
              <img src="http://placehold.jp/150x150.png" class="img-icon">
              {{ else }}
              <img src="{{ .filepath }}" width="150px" height="150px" class="img-icon preview1">
              {{end}}
              <div class=""></div>
            </td>

go本体では、if .filepath == “” という順番でかけるのに、templateでは if eq と書かなければダメなのは面白いですね。いっその事統一して欲しいですが…

[Go Revel] FileのUploadと画像の保存

test.html

<h1>テストデータのアップロードページ</h1>
    <form name="upload" method="POST" action="/test/upload" enctype="multipart/form-data">
      <input type="file"   name="imgFile"  accept="image/jpeg, image/png">
      <input type="submit" name="submit"   value="commit">
    </form>

App.go

import (
	"github.com/revel/revel"
	"app/app/models"
	"fmt"
	"os"
)

func (c App) Test() revel.Result {

	return c.Render()
}

func (c App) Upload(imgFile *os.File) revel.Result {
  fmt.Printf("imgFile => %v\n", imgFile)
  return c.Redirect(App.Test)
}

imgFile => &{0xc0000a85a0}

### 画像の保存
– ioutil.WriteFile(path, data, permission)で画像を保存する

import (
	"github.com/revel/revel"
	"app/app/models"
	"fmt"
	"io/ioutil"
	"os"
)

func (c App) Upload(imgFile *os.File) revel.Result {
  fmt.Println(c.Params.Files["imgFile"][0].Filename)
  name := c.Params.Files["imgFile"][0].Filename
  data, e := ioutil.ReadAll(imgFile)
  if e != nil {
	fmt.Println("error")
  }
  ioutil.WriteFile("public/tmp/" + name, data, 755)

  return c.Redirect(App.Test)

}

おおお、なんか久しぶりに感動した。

[Go Revel] パスワード変更のバリデーション実装方法

html
L 新しいパスワードと、パスワード確認のフォームを用意する
L radioボタンでchangeなら表示、not changeなら非表示

<tr>
            <td>Password</td>
            <td><input type="radio" name="password_change" value="n" checked>Not Change <input type="radio" name="password_change" value="y">Change
            <div class="changeterm displaynone">
              <input type="password" name="password" class="input" placeholder="new password"><br>
              <input type="password" name="password_confirm" class="input" placeholder="new password confirm">
                    </div>
            </td>
          </tr>

app.go
  L passwordが4文字以上の場合は、Min()を使う
   L if文でpasswordとpassword_confirmが一致かではなく、requiredの中で一致するか確認する

func (c App) MypageConfirm(email string, message string, password string, password_confirm string) revel.Result {

	// 省略
	if len(password) > 0 {
		c.Validation.Min(len(password), 4).Message("Password must be more than 4 character")
		c.Validation.Required(password == password_confirm).Message("password and password confirmation must be same.")
	}

	// 省略
	return c.Render(name, email, message)
}

if文で書きたいところだが、変わってるなー

[Go Revel] CSRF対策を行う

### revel-csrfをインストール
$ go get github.com/cbonello/revel-csrf

app/init.go

import (
	"github.com/revel/revel"
	"prd/app/controllers"
	"github.com/cbonello/revel-csrf" // 追加
	_ "github.com/revel/modules"

)
	revel.Filters = []revel.Filter{
		// 省略
		csrf.CSRFFilter, 			   // 追加
	}

*.html

      <form action="/mypage-confirm" method="post" enctype="multipart/form-data">
      <input type="hidden" name="csrf_token" value="{{ .csrf_token }}" />
      // 省略

CSRFがないと何か落ち着きませんが、あって良かったです。

[Go Revel] Emailのバリデーション

c.Validation.Email(email)と書く

app.go

func (c App) MypageConfirm(email string, message string) revel.Result {

	name := c.Session["userName"]
	c.Validation.Required(email).Message("Email is required")
	c.Validation.Email(email).Message("Please enter in email format")

	if c.Validation.HasErrors(){
		c.Validation.Keep()
		c.FlashParams()
		return c.Redirect(App.Mypage)
	}

	return c.Render(name, email, message)
}

うん、良さそうではある

[Go Revel] テンプレートでナビテーションを一元管理する

Go Revelでnavigationとside menuをテンプレート化したい
-> classでページによってis-activeと付与する箇所は、テンプレート側でセットできるようにする。

/app/views/menu.html

  <section class="main-content columns is-fullheight">
  <aside class="column is-3 is-narrow-mobile is-fullheight section is-hidden-mobile">
    <p class="menu-label is-hidden-touch">MENU</p>
    <ul class="menu-list">
      <li>
        <a href="/home" class="{{.home}}">
          <span class="icon"><i class="fa fa-home"></i></span> Home
        </a>
      </li>
      <li>
        <a href="#" class="">
          <span class="icon"><i class="fa fa-table"></i></span> Channel
        </a>

        <ul>
          <li>
            <a href="#" class="{{.ch1}}">
              <span class="icon is-small"><i class="fa fa-link"></i></span> Ch1
            </a>
          </li>
          <li>
            <a href="#" class="{{.ch2}}">
              <span class="icon is-small"><i class="fa fa-link"></i></span> Ch2
            </a>
          </li>
          <li>
            <a href="#" class="{{.ch3}}">
              <span class="icon is-small"><i class="fa fa-link"></i></span> Ch3
            </a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#" class="{{.mypage}}">
          <span class="icon"><i class="fa fa-id-card"></i></span> MyPage
        </a>
      </li>
      <li>
        <a href="#" class="{{.doc}}">
          <span class="icon"><i class="fa fa-file-alt"></i></span> Document
        </a>
      </li>
      <li>
        <a href="#" class="{{.chat}}">
          <span class="icon"><i class="fa fa-comments"></i></span> Chat room
        </a>
      </li>
    </ul>
  </aside>

/app/views/App/Index.html

{{template "nav.html" .}}
{{set . "home" "is-active"}}
{{template "menu.html" .}}

/app/views/App/mypage.html

{{template "nav.html" .}}
{{set . "mapage" "is-active"}}
{{template "menu.html" .}}

昔1000ページぐらいのサイトを運営してた時に、12/31に1000ページのall right reserved 20XXを1000ページ修正してたの思い出した🤮🤮🤮
絶対テンプレートを使う

[Go言語] revelでchatを作る

conf/routes

GET		/websocket/room		  WebSocket.Room
WS      /websocket/room/socket   WebSocket.RoomSocket 

app/views/WebSocket/room.html

{{set . "title" "Chat room"}}
{{template "header.html" .}}

<h1>WebSocket - You are now chatting as {{.user}} <a href="/">Leave</a></h1>

<div id="thread">
	<script type="text/html" id="message_tmpl">
		{{raw "<%"}} if(event.Type == 'message') { %>
			<div class="message <%= event.User == '{{.user}}' ? 'you' : '' %>">
				<h2>{{raw "<%"}}= event.User %></h2>
				<p>
					{{raw "<%"}}= event.Text %>
				</p>
			</div>
		{{raw "<%"}} } %>
		{{raw "<%"}} if(event.Type == 'join') { %>
			<div class="message notice">
				<h2></h2>
				<p>
					{{raw "<%"}}= event.User %> joined the room
				</p>
			</div>
		{{raw "<%"}} }  %>
		{{raw "<%"}} if(event.Type == 'leave') { %>
			<div class="message notice">
				<h2></h2>
				<p>
					{{raw "<%"}}= event.User %> left the room
				</p>
			</div>
		{{raw "<%"}} }  %>
		{{raw "<%"}} if(event.Type == 'quit') { %>
			<div class="message important">
				<h2></h2>
				<p>
					You are disconnected!
				</p>
			</div>
		{{raw "<%"}} }  %>
	</script>
</div>

<div id="newMessage">
	<input type="text" id="message" autocomplete="off" autofocus>
	<input type="submit" value="send" id="send">
</div>

<script type="text/javascript">

	var socket = new WebSocket('ws://'+window.location.host+'/websocket/room/socket?user={{.user}}')

	var display = function(event){
		$('#thread').append(tmpl('message_tmpl', {event: event}));
		$('#thread').scrollTo('max')
	}

	socket.onmessage = function(event){
		display(JSON.parse(event.data))
	}

	$('#send').click(function(e){
		var message = $('#message').val()
		$('#message').val('')
		socket.send(JSON.stringify(message))
	})

	$('#message').keypress(function(e){
		if(e.charCode == 13 || e.keyCode == 13){
			$('#send').click()
			e.preventDefault()
		}
	})
</script>

app/views/header.html

    <script src="/public/js/jquery.scrollTo-min.js"></script>
    <script src="/public/js/templating.js"></script>

chatroom.go

package chatroom

import (
	"container/list"
	"time"
)

type Event struct {
	Type string
	User string
	Timestamp int
	Text string
}

type Subscription struct {
	Archive []Event
	New <-chan Event
}

func (s Subscription) Cancel(){
	unsubscribe <- s.New
	drain(s.New)
}

func newEvent(typ, user, msg string) Event {
	return Event{typ, user, int(time.Now().Unix()), msg}
}

func Subscribe() Subscription {
	resp := make(chan Subscription)
	subscribe <- resp
	return <- resp
}

func Join(user string){
	publish <- newEvent("join", user, "")
}

func Say(user, message string){
	publish <- newEvent("message", user, message)
}

func Leave(user string){
	publish <- newEvent("leave", user, "")
}

const archiveSize = 10

var (
	subscribe = make(chan (chan<- Subscription), 10)

	unsubscribe = make(chan (<-chan Event), 10)
	publish = make(chan Event, 10)
)

func chatroom(){
	archive := list.New()
	subscribers := list.New()

	for {
		select {
		case ch := <-subscribe:
			var events []Event
			for e := archive.Front(); e != nil; e = e.Next(){
				events = append(events, e.Value.(Event))
			}
			subscriber := make(chan Event, 10)
			subscribers.PushBack(subscriber)
			ch <- Subscription{events, subscriber}
		case event := <-publish:
			for ch := subscribers.Front(); ch != nil; ch = ch.Next(){
				ch.Value.(chan Event) <- event
			}
			if archive.Len() >= archiveSize {
				archive.Remove(archive.Front())
			}
			archive.PushBack(event)
		case unsub := <-unsubscribe:
			for ch := subscribers.Front(); ch != nil; ch = ch.Next(){
				if ch.Value.(chan Event) == unsub {
					subscribers.Remove(ch)
					break
				}
			}
		}
	}
}

func init(){
	go chatroom()
}

func drain(ch <-chan Event){
	for {
		select {
		case _, ok := <-ch:
			if !ok {
				return
			}
		default:
			return
		}
	}
}

websocket.go

package controllers

import (
	"github.com/revel/revel"
	"app/app/chatroom"
)

type WebSocket struct {
	*revel.Controller
}

func (c WebSocket) Room(user string) revel.Result {
	user = "hpscript"
	return c.Render(user)
}

func (c WebSocket) RoomSocket(user string, ws revel.ServerWebSocket) revel.Result {

	if ws == nil {
		return nil
	}

	subscription := chatroom.Subscribe()
	defer subscription.Cancel()

	chatroom.Join(user)
	defer chatroom.Leave(user)

	for _, event := range subscription.Archive {
		if ws.MessageSendJSON(&event) != nil {
			return nil
		}
	}

	newMessages := make(chan string)
	go func(){
		var msg string
		for {
			err := ws.MessageReceiveJSON(&msg)
			if err != nil {
				close(newMessages)
				return
			}
			newMessages <- msg
		}
	}()

	for {
		select {
		case event := <-subscription.New:
			if ws.MessageSendJSON(&event) != nil {
				return nil
			}
		case msg, ok := <-newMessages:
			if !ok {
				return nil
			}
			chatroom.Say(user, msg)
		}
	}
	return nil
}

public/js/templating.js

{{set . "title" "Chat room"}}
{{template "header.html" .}}

<h1>WebSocket - You are now chatting as {{.user}} <a href="/">Leave</a></h1>

<div id="thread">
	<script type="text/html" id="message_tmpl">
		{{raw "<%"}} if(event.Type == 'message') { %>
			<div class="message <%= event.User == '{{.user}}' ? 'you' : '' %>">
				<h2>{{raw "<%"}}= event.User %></h2>
				<p>
					{{raw "<%"}}= event.Text %>
				</p>
			</div>
		{{raw "<%"}} } %>
		{{raw "<%"}} if(event.Type == 'join') { %>
			<div class="message notice">
				<h2></h2>
				<p>
					{{raw "<%"}}= event.User %> joined the room
				</p>
			</div>
		{{raw "<%"}} }  %>
		{{raw "<%"}} if(event.Type == 'leave') { %>
			<div class="message notice">
				<h2></h2>
				<p>
					{{raw "<%"}}= event.User %> left the room
				</p>
			</div>
		{{raw "<%"}} }  %>
		{{raw "<%"}} if(event.Type == 'quit') { %>
			<div class="message important">
				<h2></h2>
				<p>
					You are disconnected!
				</p>
			</div>
		{{raw "<%"}} }  %>
	</script>
</div>

<div id="newMessage">
	<input type="text" id="message" autocomplete="off" autofocus>
	<input type="submit" value="send" id="send">
</div>

<script type="text/javascript">

	var socket = new WebSocket('ws://'+window.location.host+'/websocket/room/socket?user={{.user}}')

	var display = function(event){
		$('#thread').append(tmpl('message_tmpl', {event: event}));
		$('#thread').scrollTo('max')
	}

	socket.onmessage = function(event){
		display(JSON.parse(event.data))
	}

	$('#send').click(function(e){
		var message = $('#message').val()
		$('#message').val('')
		socket.send(JSON.stringify(message))
	})

	$('#message').keypress(function(e){
		if(e.charCode == 13 || e.keyCode == 13){
			$('#send').click()
			e.preventDefault()
		}
	})
</script>

<% ●● %>: 構文
<%= ■■ %>: 出力

これは凄し