Last active
February 26, 2016 13:29
-
-
Save tyru/b0d908997003cad43331 to your computer and use it in GitHub Desktop.
Spring Boot + AngularJS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.controller.api; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.util.Date; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Properties; | |
import javax.mail.Message; | |
import javax.mail.MessagingException; | |
import javax.mail.Session; | |
import javax.mail.Transport; | |
import javax.mail.internet.InternetAddress; | |
import javax.mail.internet.MimeMessage; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.web.bind.annotation.PathVariable; | |
import org.springframework.web.bind.annotation.RequestBody; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.RequestMethod; | |
import org.springframework.web.bind.annotation.RestController; | |
import lombok.AllArgsConstructor; | |
import lombok.Data; | |
import lombok.NoArgsConstructor; | |
@RestController | |
@RequestMapping("/api") | |
public class ApiController { | |
private static final String PROP_FILE = "mail.properties"; | |
private Map<String, String> config; | |
private static final String PROP_MAIL_FROM_ADDRESS = "mail.from-address"; | |
private static final String PROP_MAIL_DEBUG = "mail.debug"; | |
private static final String PROP_SMTP_HOST = "mail.smtp-host"; | |
private static final String PROP_MAIL_TITLE = "mail.title"; | |
private static final Logger logger = LoggerFactory.getLogger(ApiController.class); | |
@Autowired | |
private AddressRepository addressRepos; | |
/** | |
* Load config. | |
*/ | |
public ApiController() { | |
logger.info("Constructing ApiController..."); | |
Properties prop = new Properties(); | |
final InputStream in = ApiController.class.getClassLoader().getResourceAsStream(PROP_FILE); | |
if (in == null) { | |
throw new IllegalArgumentException(PROP_FILE + " not found."); | |
} | |
try { | |
prop.load(in); | |
config = new HashMap<>(); | |
for (final String key : prop.stringPropertyNames()) { | |
final String value = prop.getProperty(key); | |
config.put(key, value); | |
} | |
} catch (IOException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} finally { | |
try { | |
in.close(); | |
} catch (IOException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
} | |
} | |
@RequestMapping(method = RequestMethod.GET, value = "/address") | |
public List<Address> getAddressList() { | |
logger.info("GET /address"); | |
return addressRepos.findAll(); | |
} | |
@RequestMapping(method = RequestMethod.GET, value = "/address/{id:^(?:[1-9]|[1-9][0-9]*)$}") | |
public Address getAddressById(@PathVariable long id) { | |
logger.info("GET /address/" + id); | |
final Address address = addressRepos.findAddressById(id); | |
if (address != null) { | |
return address; | |
} else { | |
throw new AddressNotFound(id); | |
} | |
} | |
@RequestMapping(method = RequestMethod.POST, value = "/mail") | |
public boolean postMail(@RequestBody Mail mail) { | |
logger.info("POST /mail"); | |
logger.debug(mail.toString()); | |
Properties props = new Properties(); | |
props.put("mail.smtp.host", config.get(PROP_SMTP_HOST)); | |
final boolean mailDebug = Boolean.valueOf(config.get(PROP_MAIL_DEBUG)); | |
if (mailDebug) { | |
props.put("mail.debug", "true"); | |
} | |
Session session = Session.getInstance(props, null); | |
session.setDebug(mailDebug); | |
try { | |
Message msg = new MimeMessage(session); | |
final String fromAddress = config.get(PROP_MAIL_FROM_ADDRESS); | |
msg.setFrom(new InternetAddress(fromAddress)); | |
InternetAddress[] address = new InternetAddress[mail.getIds().size()]; | |
for (int i = 0; i < mail.getIds().size(); i++) { | |
String addr = getAddressById(mail.getIds().get(i)).getAddress(); | |
address[i] = new InternetAddress(addr); | |
} | |
msg.setRecipients(Message.RecipientType.TO, address); | |
msg.setSubject(config.get(PROP_MAIL_TITLE)); | |
msg.setSentDate(new Date()); | |
msg.setText(mail.getBody()); | |
Transport.send(msg); | |
} catch (MessagingException e) { | |
e.printStackTrace(); | |
} | |
return true; | |
} | |
} | |
@Data | |
@NoArgsConstructor | |
@AllArgsConstructor | |
class Mail { | |
private List<Long> ids; | |
private String body; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.boot.CommandLineRunner; | |
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
import org.springframework.context.annotation.Bean; | |
import com.example.controller.api.Address; | |
import com.example.controller.api.AddressRepository; | |
@SpringBootApplication | |
public class Application { | |
private static final Logger logger = LoggerFactory.getLogger(Application.class); | |
public static void main(String[] args) { | |
SpringApplication.run(Application.class, args); | |
} | |
@Bean | |
public CommandLineRunner demo(AddressRepository repository) { | |
return (args) -> { | |
logger.info("Saving demo data..."); | |
repository.save(new Address("拓也", "tyru.exe+takuya@gmail.com")); | |
repository.save(new Address("ほげら", "tyru.exe+hogera@gmail.com")); | |
repository.save(new Address("もげら", "tyru.exe+mogera@gmail.com")); | |
repository.save(new Address("はなもげら", "tyru.exe+hanamogera@gmail.com")); | |
logger.info("Saving demo data...Done."); | |
}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mail.from-address=tyru.exe+angular.demo@gmail.com | |
mail.debug=true | |
mail.smtp-host=smtp | |
mail.title=こんにちは |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html ng-app="mailApp"> | |
<head ng-controller="htmlHeaderCtrl"> | |
<meta charset="utf-8" /> | |
<title>Getting Started Angular</title> | |
<script type="text/javascript" src="/js/ext/angular.min.js"></script> | |
<script type="text/javascript" src="/js/ext/angular-route.min.js"></script> | |
<script type="text/javascript" src="/js/ext/underscore-min.js"></script> | |
<script type="text/javascript" src="/js/mail.js"></script> | |
<link rel="stylesheet" href="/css/mail/common.css" /> | |
<link rel="stylesheet" ng-href="/css/mail/{{css}}" | |
ng-repeat="css in shared.optCssList" /> | |
<base href="/mail/"> | |
</head> | |
<body> | |
<!-- | |
大まかすぎる画面遷移の流れ | |
・メールを書く | |
・あて先を書く | |
・メール送信 | |
ばあちゃんでもわかるようになるべく手紙のレイアウトに似せる | |
--> | |
<!-- ヘッダー:画面遷移させるボタン等を配置 --> | |
<div id="header" ng-controller="headerCtrl"> | |
<!-- XXX: | |
「次へ」ボタンが手元のリモコンではなく | |
Web画面上に表示されている意味はあるのか? | |
(現在入力用デバイスに何を使うかは検討中) --> | |
<!-- TODO: | |
メールを書く時など通常操作時はヘッダーはなるべく表示しない。 | |
何か専用のボタンが押された時のみ表示する。 --> | |
<span id="next-btn" ng-class="{disable: shared.isDisabledNextURL()}" class="item"> | |
<a href="{{shared.nextURL}}" ng-click="handleNextButtonClick()">次に進む</a> | |
</span> | |
<h1 id="title">{{shared.title}}</h1> | |
<!-- TODO --><span id="help-btn" class="item">困ったときは</span> | |
</div> | |
<!-- メインの表示領域:URLによってビューを切り替える --> | |
<div id="container" ng-view></div> | |
<script type="text/ng-template" id="mailEdit.html"> | |
<!-- hidden element --> | |
<textarea id="textarea-body" ng-model="shared.mailBody" ng-blur="focusTextArea()"></textarea> | |
<ul id="body"> | |
<!-- TODO: 適切な字数で改行(ラップアラウンド)する --> | |
<li class="line" ng-repeat="line in lines"><span ng-if="line.text">{{line.text}}</span><span ng-if="!line.text"> </span></li> | |
</ul> | |
</script> | |
<script type="text/ng-template" id="addrEdit.html"> | |
<p ng-if="!addrList">連絡先を読み込んでいます...</p> | |
<div ng-attr-id="address-{{addr.id}}" ng-class="addr.selected ? '' : 'disable'" class="address" ng-repeat="addr in addrList"> | |
<a ng-click="addrList[$index].selected = ! addrList[$index].selected">{{addr.name}}</a> | |
</div> | |
</script> | |
<script type="text/ng-template" id="mailSentCtrl.html"> | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
"use strict"; | |
var app = angular.module("mailApp", ['ngRoute']); | |
// FIXME: Cursor string is placed same as mail text, but underline. | |
var ENABLE_INSERT_CURSOR = false; | |
var CURSOR_STRING = '_'; | |
var DISABLED_NEXT_URL = '#'; | |
// Router, and so on | |
app.config(['$locationProvider', '$routeProvider', function($locationProvider,$routeProvider) { | |
$locationProvider.html5Mode(true); | |
$routeProvider.when('/mailEdit', { | |
templateUrl: 'mailEdit.html', | |
controller: 'mailEditCtrl' | |
}) | |
$routeProvider.when('/addrEdit', { | |
templateUrl: 'addrEdit.html', | |
controller: 'addrEditCtrl' | |
}) | |
$routeProvider.otherwise({ | |
redirectTo: '/mailEdit' | |
}); | |
}]); | |
// Shared structure between controllers | |
app.factory("SharedService", function() { | |
return { | |
set: function(items) { | |
if (this === window) { | |
throw new 'Internal Error: call set() like this: $scope.shared.set(...)'; | |
} | |
// Set default | |
this.title = ''; | |
this.nextURL = DISABLED_NEXT_URL; | |
this.optCssList = []; | |
this.handleNextButtonClick = function() {}; | |
// FIXME: ページを戻った時に備えてクリアはしないでおきたいが、これだけクリアしないのは何か気持ち悪い | |
//this.mailBody = ''; | |
// Set given values | |
for (var key in items) { | |
this[key] = items[key]; | |
} | |
}, | |
isDisabledNextURL: function() { | |
return this.nextURL === DISABLED_NEXT_URL; | |
}, | |
title: '', | |
nextURL: DISABLED_NEXT_URL, | |
optCssList: [], | |
handleNextButtonClick: function() { return true; }, | |
mailBody: '' | |
}; | |
}); | |
// For <head>, additional css list | |
app.controller('htmlHeaderCtrl', function ($scope, SharedService) { | |
$scope.shared = SharedService; | |
}); | |
// Header, title, and so on | |
app.controller('headerCtrl', function ($scope, SharedService) { | |
$scope.shared = SharedService; | |
$scope.handleNextButtonClick = function() { | |
return $scope.shared.handleNextButtonClick(); | |
}; | |
}); | |
// Mail edit page | |
app.controller('mailEditCtrl', function ($scope, SharedService) { | |
$scope.shared = SharedService; | |
$scope.shared.set({ | |
title: '文章を書きます。', | |
optCssList: ['mailEditCtrl.css'], | |
nextURL: 'addrEdit' | |
}); | |
$scope.$watch('shared.mailBody', function () { | |
// FIXME: This callback is not called when Enter is pressed. | |
var bodyText = $scope.shared.mailBody; | |
// Insert cursor string at cursorPos. | |
if (ENABLE_INSERT_CURSOR) { | |
var cursorPos = document.getElementById('textarea-body').selectionStart; | |
bodyText = bodyText.substr(0, cursorPos) + CURSOR_STRING + bodyText.substr(cursorPos); | |
} | |
$scope.lines = bodyText.split('\n').map(function(line) { | |
return {text: line}; | |
}); | |
}) | |
$scope.focusTextArea = function() { | |
document.getElementById('textarea-body').focus(); | |
}; | |
$scope.focusTextArea(); | |
}); | |
// Address edit page | |
app.controller('addrEditCtrl', function ($scope, SharedService, $http) { | |
var ENABLED_NEXT_URL = 'mailSentCtrl'; | |
$scope.shared = SharedService; | |
$scope.shared.set({ | |
title: '誰に送りますか?', | |
optCssList: ['addrEditCtrl.css'], | |
nextURL: DISABLED_NEXT_URL, | |
handleNextButtonClick: function() { | |
if ($scope.shared.nextURL === DISABLED_NEXT_URL) { | |
alert("送る相手を選んでください。"); | |
return false; | |
} | |
var data = { | |
ids: _.pluck(_.select($scope.addrList, function(address) { | |
return address.selected; | |
}), 'id'), | |
body: $scope.shared.mailBody | |
}; | |
$http.post('/api/mail', data).success(function(result) { | |
console.dir(result); | |
}); | |
return true; | |
} | |
}); | |
$scope.addrList = []; | |
// Fetch address list data from backend server. | |
$http.get('/api/address').success(function(data) { | |
$scope.addrList = _.map(data, function(val) { | |
val.selected = false; | |
return val; | |
}); | |
$scope.$watch('addrList', function(newValue, oldValue) { | |
function isSelected(val) { | |
return val.selected; | |
} | |
// Also apply to next button. | |
if (_.any(newValue, isSelected)) { | |
$scope.shared.nextURL = ENABLED_NEXT_URL; | |
} else { | |
$scope.shared.nextURL = DISABLED_NEXT_URL; | |
} | |
}, true); | |
}); | |
}); | |
// Mail sent page | |
app.controller('mailSentCtrl', function ($scope, SharedService) { | |
$scope.shared = SharedService; | |
$scope.shared.set({ | |
title: 'メールを投函しました。', | |
optCssList: [], | |
nextURL: DISABLED_NEXT_URL | |
}); | |
}); | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.controller.mail; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.stereotype.Controller; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
@Controller | |
@RequestMapping("/mail") | |
public class MailController { | |
private static final Logger logger = LoggerFactory.getLogger(MailController.class); | |
@RequestMapping("/") | |
public String exampleIndex() { | |
logger.info("GET /mail"); | |
return "mail"; | |
} | |
@RequestMapping("/**") | |
public String exampleOther() { | |
logger.info("GET /mail/**"); | |
// NOTE: Redirects to '/mail' even when a user reloads a page | |
// which is any page under /mail. | |
return "redirect:/mail/"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment