ããã«ã¡ã¯ãHabrã
å€ãè¿ã¥ããŠããŠãäŒæãè¿ã¥ããŠãããç§ã®ææãJavaãã©ãããã©ãŒã ã§Webã¢ããªã±ãŒã·ã§ã³ãäœæããçµéšãå
±æããããã®ç©ºãæéãçŸããŸããã ã¡ã€ã³èšèªãšããŠãScalaã䜿çšããŸãã ããã¯å°ããªã¬ã€ãã®ãããªãã®ã§ãJavaã®çµéšãæã€äººãåŸã
ã«Scalaã䜿ãå§ããæ¢åã®ææãæŸæ£ããªãæ¹æ³ã§ãã
ããã¯ãã¢ããªã±ãŒã·ã§ã³ã®åºæ¬æ§é ã«æ³šæãæãäžé£ã®èšäºã®æåã®éšåã§ãã ããã¯ãSpringãHibernateãJPAãJSPãããã³ãã®ä»ã®3ã4æåã®ç¥èªã§äœæ¥ããŠããJavaãç¥ã£ãŠãã人ã
ã察象ãšããŠããŸãã ãããžã§ã¯ãã§Scalaãå¯èœãªéãè¿
éãã€ç°¡åã«äœ¿çšãå§ããæ°ããã¢ããªã±ãŒã·ã§ã³ãå¥ã®æ¹æ³ã§èšèšããæ¹æ³ã説æããŸãã ãããã¯ãã¹ãŠãããžã§ã¯ãã®åšãã§è¡ãããå€ãã®èŠä»¶ãæºããå¿
èŠããããŸãã
1.ã¢ããªã±ãŒã·ã§ã³ã¯å®å
šã«éããããæ¿èªåŸã«ã®ã¿åäœããŸã
2.䟿å©ãªAPIã®ååšïŒRESTã¯å¿ããŠããŸããŸãïŒå±¥æŽã¯æ¢ã«ãããŸãïŒãSQLã®ãããªã¯ãšãªã䜿çšããŠãGoogle AdWords APIã®ãããªãã®ãèšè¿°ããŸãïŒ
3.ã¢ããªã±ãŒã·ã§ã³ãµãŒããŒãªãã§å®è¡ããæ©èœ
4.i18n
5.ããŒã¿ããŒã¹ã®ç§»è¡
6.
Vagrantãä»ããŠéçºç°å¢ãå±éããå¿
èŠããããŸã
7.ãããŠãããããªããšã«ããã®ã³ã°ããããã€ã¡ã³ã...
ããã¯ãã¹ãŠãã¢ããªã±ãŒã·ã§ã³ã®ä»éãšéçºãéåžžã«ç°¡åã«ãªãããã«è¡ãå¿
èŠããããŸãããã®ãããæ°ãããã£ã¬ã¯ããªã远å ãããšãã«ããã°ã©ãã2æ¥éãããè©äŸ¡ãããããªç¶æ³ã¯ãããŸããã ç§ãããªãã«èå³ããããªããç«ããé¡ãããŸãã
å§ããã«ã¯
ããšãã°ã
ãã«ã¹ããã³ã®èæž
Scala for the Impatientã
ããããªã©ãScalaã®æ§æã«æ
£ããå¿
èŠããããŸãã èšèªãã©ã®ããã«æ©èœãããã倧ãŸãã«æ³åãããã®äžã«äœãããããç¥ãããã ãžã£ã³ã°ã«ã«çŽè¡ãããã·ã³ãã«ã«å§ããŠãè€éã§é¢çœããã¶ã€ã³ãèŠãå ŽæãèŠããŠããããã«ã¢ããã€ã¹ããŸãã ãã°ããããŠããããããã«æ»ã£ãŠå®è£
æ¹æ³ã確èªãããã®ãããªããšã詊ããŠãã ããã èšèªã倧ãããããã«ãã¹ãŠã®æ©èœã䜿çšãããšåé¡ãçºçããå¯èœæ§ããããŸãã
䜿çšãããã®
Scalaã«ã¯ãããšãã°
Playãã¬ãŒã ã¯ãŒã¯ ã
SBT ã
Slick ã
LiftãèŠã䟡å€ããããŸãã ãããããã§ã«åãçµãã§ãããã®ããå§ããŸãã Mavenãä»ããŠã¢ã»ã³ããªãè¡ããŸãã SpringãSpring MVCãSpring Securityãåºç€ãšããŠãã ããã ããŒã¿ããŒã¹ã«ã€ããŠã¯ã
Squerylã䜿çšããŠã¿ãŸãããïŒãã®éããç¹å®ã®æ©èœãåžžã«åé¡ã®ããLazyã®ããã§Hibernateã¯å¥œãã§ã¯ãããŸããïŒã ç§ãã¡ã®åç·ã¯å®å
šã«Angularã«ãªããã¹ã¿ã€ã«ã«ã€ããŠã¯SASSã䜿çšããJSã§ã¯ãªãCoffeeScriptã䜿çšããŸãïŒäœ¿ç𿹿³ã瀺ããŸãããåãããã«CoffeeãæåŠã§ããŸãïŒã ãã¡ããã
ScalaTestã§çµ±åãšã¢ãžã¥ãŒã«ã®äž¡æ¹ã®ãã¹ããäœæããŸãã ããã¯ç¬èªã®ç¹æ§ãåããå¥åã®ããªã¥ãŒã äŒè©±ã§ãããããåé¢ã®ãã¹ãã¯çç¥ããŸãã APIã¯ç§ãã¡ã«ãšã£ãŠè峿·±ããã®ã«ãªããŸãã ãµãŒãã¹ã®æŠå¿µãããããµãŒãã¹ã«ã¯ã¡ãœããããããã¯ãšãªã¯ãšãªã®ãããªSQLããµããŒãããŸãã äŸïŒ
select id, name, bank from Organization where bank.id = :id // => [{id: 1, name: 'name', bank: {id: 1, name: 'bankname', node: 'Note'}}] select name, bank.id, bank.name from Organization order by bank.name // => [{name: 'name', bank: {id: 1, name: 'bankname'}}]
ããžãã¹ãž
æ§é ãšäŸåé¢ä¿
ãŸããMavenãããžã§ã¯ããäœæããããã«Scalaãã³ã³ãã€ã«ããããã®ãã©ã°ã€ã³ããã©ã°ã€ã³ããŸãã
pom.xml <properties> <scala-version>2.10.4</scala-version> </properties> <dependencies> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala-version}</version> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> <version>3.1.6</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> </plugin> </plugins> </pluginManagement> <plugins> <plugin> <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> <executions> <execution> <id>scala-compile-first</id> <phase>process-resources</phase> <goals> <goal>add-source</goal> <goal>compile</goal> </goals> </execution> <execution> <id>scala-test-compile</id> <phase>process-test-resources</phase> <goals> <goal>testCompile</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <executions> <execution> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
ãã¹ãŠã®ãœãŒã¹ã¯
src / main / scalaãã£ã¬ã¯ããªã«ãããŸãããŸããJavaã§èšè¿°ããŠ
src / main / javaã«çœ®ãããšãã§ããŸãã å®éããã®ãããªæ©äŒãå¿
èŠãªå ŽåãScalaã¯ã©ã¹ã¯Javaã¯ã©ã¹ã§äœ¿çšã§ãããã®éãå¯èœã§ãã ãŸããSpringãSpring MVCãSpring SecurityãSpring OAuthãå¿
èŠã«ãªããŸããããããã¹ãŠãæ¥ç¶ããã®ã¯é£ãããªããšæãã®ã§ã説æããŸããã ãã¥ã¢ã³ã¹ã®ãã¡ãJettyãå¿
èŠã«ãªããŸãïŒéçºäžã«ã¢ããªã±ãŒã·ã§ã³ãå®è¡ããŸãïŒã ãã®ä»ã®Scala ConfigãScalaTestã ãã¹ããMavenã§å®è¡ããã«ã¯ãMaven Surefireãã©ã°ã€ã³ããªãã«ããŠãScalatest Mavenãã©ã°ã€ã³ã䜿çšããå¿
èŠããããŸã
pom.xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.7</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> <plugin> <groupId>org.scalatest</groupId> <artifactId>scalatest-maven-plugin</artifactId> <version>1.0</version> <configuration> <reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory> <junitxml>.</junitxml> <filereports>WDF TestSuite.txt</filereports> </configuration> <executions> <execution> <id>test</id> <goals> <goal>test</goal> </goals> </execution> </executions> </plugin>
åã¯ã©ã¹ã§ãã¬ãŒã®åæåãèšè¿°ããªãããã«ã
ç¹æ§LazyLoggingãæäŸããã©ã€ãã©ãªãæ¥ç¶ããŸãã
<dependency> <groupId>com.typesafe.scala-logging</groupId> <artifactId>scala-logging-slf4j_2.10</artifactId> <version>2.1.2</version> </dependency>
ããŒã¿ããŒã¹ã®ç§»è¡
次ã«ãããŒã¿ããŒã¹ã«ã€ããŠèããŸãã ç§»è¡ã«ã¯
Liquibaseã䜿çšã
ãŸã ã æåã«ããã¹ãŠã®å€æŽã»ãããžã®ãªã³ã¯ãèšè¿°ãããã¡ã€ã«ãäœæããŸãã
ãªãœãŒã¹/ changelog / db.changelog-master.xml <?xml version="1.0" encoding="UTF-8"?> <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> <include file="classpath:changelog/db.changelog-0.1.xml"/> </databaseChangeLog>
ãããŠãæåã®å€æŽã»ããã«ã€ããŠèª¬æããŸããããã«ã¯ãæ¿èªãšOAuthã®ãã¹ãŠã®ããŒãã«ããããŸã
db.changelog-0.1.xml <?xml version="1.0" encoding="UTF-8"?> <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> <changeSet id="0.1-auth" author="andy.sumskoy@gmail.com"> <createTable tableName="users"> <column name="user_id" type="int" autoIncrement="true"> <constraints primaryKey="true" nullable="false"/> </column> <column name="username" type="varchar(255)"> <constraints unique="true" nullable="false"/> </column> <column name="password" type="varchar(255)"> <constraints nullable="false"/> </column> <column name="enabled" type="boolean" defaultValueBoolean="true"> <constraints nullable="false"/> </column> </createTable> <createTable tableName="authority"> <column name="authority_id" type="int" autoIncrement="true"> <constraints primaryKey="true" nullable="false"/> </column> <column name="name" type="varchar(255)"> <constraints unique="true" nullable="false"/> </column> </createTable> <createTable tableName="user_authorities"> <column name="user_id" type="int"> <constraints foreignKeyName="fk_user_authorities_users" referencedTableName="users" referencedColumnNames="user_id"/> </column> <column name="authority_id" type="int"> <constraints foreignKeyName="fk_user_authorities_authority" referencedTableName="authority" referencedColumnNames="authority_id"/> </column> </createTable> <addPrimaryKey columnNames="user_id, authority_id" constraintName="pk_user_authorities" tableName="user_authorities"/> <insert tableName="authority"> <column name="authority_id">1</column> <column name="name">ROLE_ADMIN</column> </insert> <insert tableName="authority"> <column name="authority_id">2</column> <column name="name">ROLE_USER</column> </insert> <insert tableName="authority"> <column name="authority_id">3</column> <column name="name">ROLE_POWER_USER</column> </insert> <createTable tableName="persistent_logins"> <column name="username" type="varchar(64)"> <constraints nullable="false"/> </column> <column name="series" type="varchar(64)"> <constraints nullable="false" primaryKey="true"/> </column> <column name="token" type="varchar(64)"> <constraints nullable="false"/> </column> <column name="last_used" type="timestamp"> <constraints nullable="false"/> </column> </createTable> <createTable tableName="oauth_client_details"> <column name="client_id" type="varchar(256)"> <constraints primaryKey="true" nullable="false"/> </column> <column name="resource_ids" type="varchar(256)"/> <column name="client_secret" type="varchar(256)"/> <column name="scope" type="varchar(256)"/> <column name="authorized_grant_types" type="varchar(256)"/> <column name="web_server_redirect_uri" type="varchar(256)"/> <column name="authorities" type="varchar(256)"/> <column name="access_token_validity" type="int"/> <column name="refresh_token_validity" type="int"/> <column name="additional_information" type="text"/> <column name="autoapprove" type="varchar(256)"/> </createTable> <createTable tableName="oauth_access_token"> <column name="token_id" type="varchar(256)"/> <column name="token" type="blob"/> <column name="authentication_id" type="varchar(256)"/> <column name="user_name" type="varchar(256)"/> <column name="client_id" type="varchar(256)"/> <column name="authentication" type="blob"/> <column name="refresh_token" type="varchar(256)"/> </createTable> <createTable tableName="oauth_refresh_token"> <column name="token_id" type="varchar(256)"/> <column name="token" type="blob"/> <column name="authentication" type="blob"/> </createTable> </changeSet> <changeSet id="0.1-auth-data" author="andy.sumskoy@gmail.com" context="test"> <insert tableName="users"> <column name="user_id">1</column> <column name="username">admin</column> <column name="password">dd28a28446b96db4c2207c3488a8f93fbb843af1eeb7db5d2044e64581145341c4f1f25de48be21b </column> <column name="enabled">true</column> </insert> <insert tableName="user_authorities"> <column name="user_id">1</column> <column name="authority_id">1</column> </insert> <insert tableName="user_authorities"> <column name="user_id">1</column> <column name="authority_id">2</column> </insert> <insert tableName="user_authorities"> <column name="user_id">1</column> <column name="authority_id">3</column> </insert> <insert tableName="oauth_client_details"> <column name="client_id">simple-client</column> <column name="client_secret">simple-client-secret-key</column> <column name="authorized_grant_types">password</column> </insert> </changeSet> </databaseChangeLog>
ããã§ãã¢ããªã±ãŒã·ã§ã³ããã¹ãç°å¢ã§èµ·åãããå Žåã管çãŠãŒã¶ãŒã¯ãã¹ãŠã®å¯èœãªæš©éãæã€ç®¡çãã¹ã¯ãŒãã§ã·ã¹ãã ã«ç»é²ãããOAuthçšã®ã¯ã©ã€ã¢ã³ããäœæããããšããäºå®ã«æ³šæãã䟡å€ããããŸãã ãŸãã1ã€ã®DBMSã®ã¿ã䜿çšããå Žåã¯ãSQLã§å€æŽã»ãããäœæããããšããå§ãããŸãïŒãã
ã¯liquibaseã®ããã¥ã¡ã³ãã«èšèŒã
ããŠããŸã ïŒã
ããã§ãã¢ããªã±ãŒã·ã§ã³ã®èµ·åæã«liquibaseãããŒã¿ããŒã¹ããæšæºãã«åŒãäžããå¿
èŠããããŸãããããã«ã€ããŠã¯åŸã§è©³ãã説æããŸãã
ã¢ããªã±ãŒã·ã§ã³èšå®
ãŸãã
resources / application.confãäœæããå¿
èŠããã
ãŸã habr.template = { default = { db.url = "jdbc:postgresql://localhost/habr" db.user = "habr" db.password = "habr" } test = { db.url = "jdbc:postgresql://localhost/test-habr" } dev = { } }
ããã§ã¯ãããã€ãã®ã»ã¯ã·ã§ã³ãäœæããŸããããã©ã«ãã§ã¯ããã¹ãŠã®ããã©ã«ãèšå®ãèšå®ãããŠããŸããdevã§ã¯ããã¹ãã¯ç°å¢ã«å¿ããŠåºæã§ãã ãŸããã¢ããªã±ãŒã·ã§ã³ã®æ§æãæ
åœããAppConfigã¯ã©ã¹ãäœæããŸã
Appconfig class AppConfig { val env = scala.util.Properties.propOrElse("spring.profiles.active", scala.util.Properties.envOrElse("ENV", "test")) val conf = ConfigFactory.load() val default = conf.getConfig("habr.template.default") val config = conf.getConfig("habr.template." + env).withFallback(default) def dataSource = { val ds = new BasicDataSource ds.setDriverClassName("org.postgresql.Driver") ds.setUsername(config.getString("db.user")) ds.setPassword(config.getString("db.password")) ds.setMaxActive(20) ds.setMaxIdle(10) ds.setInitialSize(10) ds.setUrl(config.getString("db.url")) ds } def liquibase(dataSource: DataSource) = { val liquibase = new LiquibaseDropAllSupport() liquibase.setDataSource(dataSource) liquibase.setChangeLog("classpath:changelog/db.changelog-master.xml") liquibase.setContexts(env) liquibase.setShouldRun(true) liquibase.dropAllContexts += "test" liquibase } }
ã¢ããªã±ãŒã·ã§ã³ãå®è¡ãããŠããç°å¢ã決å®ããŸãã
-Dspring.profiles.active ããŸãã¯
ENVããšã¯ã¹ããŒãã§ããŸãã configãšmerjimã®å¿
èŠãªãã©ã³ããããã©ã«ãèšå®ã§ããŒãããŸãã ããŒã¿ããŒã¹æ¥ç¶ããŒã«ãäœæããŸãã ããã§ã¯ãèšå®ã§ããŒã«ãµã€ãºãäœæã§ããŸããããšãã°ããã¹ãŠããªãã·ã§ã³ã§ãã ããŠãç¹å®ã®ã©ã³ã¿ã€ã ã§ããŒã¿ããŒã¹å
ã®æ§é å
šäœãå®å
šã«åé€ããããšããµããŒãããliquibaseãäœæããŸããããšãã°ãã¢ããªã±ãŒã·ã§ã³ã§CIã䜿çšããå Žåããã¹ãŠãåé€ãããšäŸ¿å©ã§ãã Springã§DataSourceãšLiquibaseãBeanãšããŠç»é²ã§ããããã«ãªããŸãã
root.xml <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="config" class="com.sumskoy.habr.template.AppConfig"/> <bean id="dataSource" factory-bean="config" factory-method="dataSource"/> <bean id="liquibase" factory-bean="config" factory-method="liquibase"> <constructor-arg ref="dataSource"/> </bean> </beans>
Jettyã®äžããå®è¡ãã
ç§ã¯åžžã«éçºã«Jettyã䜿çšããŸããã¢ããªã±ãŒã·ã§ã³ãµãŒããŒã§èµ·åãããã³ã«é·ãåŸ
ã¡æéããªããªããŸãããŸãã倧éã®ãªãœãŒã¹ãããå Žåããã®ããã»ã¹ã«ã¯æå€§30ç§ããããéåžžã«é¢åã§ãã ã¢ããªã±ãŒã·ã§ã³ãžã®ãšã³ããªãã€ã³ããäœæããŸãã
ã¡ã€ã³ object Main extends App { val server = new Server(8080) val webAppContext = new WebAppContext() webAppContext.setResourceBase("src/main/webapp") webAppContext.setContextPath("/") webAppContext.setParentLoaderPriority(true) webAppContext.setConfigurations(Array( new WebXmlConfiguration() )) server.setHandler(webAppContext) server.start() server.join() }
å®å
šæ§
Spring Securityã®æ§ææ¹æ³ã«ã€ããŠã¯èª¬æããŸããããæ¿èªã®ããã«
/login.htmlãçŽ æµãª
URL- / index.htmlãšããŠäœ¿çšãããã¹ãŠã®APIã
/ apiãã©ã³ãã«é
眮ãããšããããšã ãã説æããŸãã
åçŽãªãŠãŒã¶ãŒã¢ãã«ãäœæãããã®ãªããžããªãäœæããŸããããã§ã¯ã1ã€ã®ã¡ãœãããããããŠãŒã¶ãŒãååã§è¿ãå¿
èŠããããŸãã çŸåšã®ãŠãŒã¶ãŒã®ååãè¿ãã³ã³ãããŒã©ãŒãäœæããŸãããïŒ
ãŠãŒã¶ãŒãšã³ãã£ã㣠case class User(username: String, password: String, enabled: Boolean, @Column("user_id") override val id: Int) extends BaseEntity { def this() = this("", "", false, 0) }
ã¢ãã«ãåè·¯ã«è¿œå ããŸãã
ã³ã¢ã¹ããŒã object CoreSchema extends Schema { val users = table[User]("users") on(users)(user => declare( user.id is autoIncremented, user.username is unique )) }
ãããŠãç°¡åãªãªããžããªãäœæããŸãã ã»ãšãã©ã®å Žåããããå¿
èŠãšãããã³ãŒããå床混乱ãããã ããªã®ã§ãå®è£
ãšã®ã€ã³ã¿ãŒãã§ãŒã¹ã¯è¡ããŸãããããã«å®è£
ãèšè¿°ããŸãã å®è£
ãçªç¶å€æŽããããAOPã䜿çšãããããå¿
èŠãããå Žåãã¯ã©ã¹ããã€ã³ã¿ãŒãã§ã€ã¹ãéžæããããšã¯é£ãããããŸããããçŸåšã¯å¿
èŠãããŸããããã®ãããªå¿
èŠæ§ã¯è¿ãå°æ¥ã«ã¯ãããŸããã ç§ãã¡ã®ç掻ãè€éã«ããªãããã«ããŸãããã
ãŠãŒã¶ãŒãªããžã㪠@Repository class UserRepository { def findOne(username: String) = inTransaction { CoreSchema.users.where(_.username === username).singleOption } }
ããŠãã·ã³ãã«ãªã³ã³ãããŒã©ãŒ
èªèšŒã³ã³ãããŒã©ãŒ @Controller @RequestMapping(Array("api/auth")) class AuthController @Autowired()(private val userRepository: UserRepository) { @RequestMapping(Array("check")) @ResponseBody def checkTokenValid(principal: Principal): Map[String, Any] = { userRepository.findOne(principal.getName) match { case Some(user) => Map[String, Any]("username" -> user.username, "enabled" -> user.enabled) case _ => throw new ObjectNotFound() } } }
JSONã§ã®ã·ãªã¢ã«åã«ã¯Jacksonã䜿çšããããšã«èšåãã䟡å€ããããŸãã ãã®ããã®ã©ã€ãã©ãªããããScalaã¯ã©ã¹ãšã³ã¬ã¯ã·ã§ã³ã§äœæ¥ããããšãã§ããŸãããã®ãããSpringã®æ£ããããããŒãå®çŸ©ããŸã
def converter() = { val messageConverter = new MappingJackson2HttpMessageConverter() val objectMapper = new ObjectMapper() with ScalaObjectMapper objectMapper.registerModule(DefaultScalaModule) messageConverter.setObjectMapper(objectMapper) messageConverter }
<beans:bean id="converter" factory-bean="config" factory-method="converter"/> <mvc:annotation-driven> <message-converters register-defaults="true"> <beans:ref bean="converter"/> </message-converters> </mvc:annotation-driven>
ãã¹ã
ããã§ããã¹ããéããŠæ¿èªåäœãä¿®æ£ããå¿
èŠããããŸãã ã¯ã©ã€ã¢ã³ãããã°ã€ã³ãã©ãŒã ãšOAuthãä»ããŠãã°ã€ã³ã§ããããšãä¿èšŒããŸãã ãã®ããã®ãã¹ããããã€ãæžããŠã¿ãŸãããã
ãŸããSpring MVCã䜿çšããŠãã¹ãŠã®ãã¹ãã®åºæ¬ã¯ã©ã¹ãäœæããŸããã
IntegrationTestSpec @ContextConfiguration(value = Array("classpath:context/root.xml", "classpath:context/mvc.xml")) @WebAppConfiguration abstract class IntegrationTestSpec extends FlatSpec with ShouldMatchers with ScalaFutures { @Resource private val springSecurityFilterChain: java.util.List[FilterChainProxy] = new util.ArrayList[FilterChainProxy]() @Autowired private val wac: WebApplicationContext = null new TestContextManager(this.getClass).prepareTestInstance(this) var builder = MockMvcBuilders.webAppContextSetup(this.wac) for(filter <- springSecurityFilterChain.asScala) builder = builder.addFilters(filter) val mockMvc = builder.build() val md = MediaType.parseMediaType("application/json;charset=UTF-8") val objectMapper = new ObjectMapper() with ScalaObjectMapper objectMapper.registerModule(DefaultScalaModule) }
ãããŠãæ¿èªã®ããã®æåã®ãã¹ããæžããŸã
it should "Login as admin through oauth with default password" in { val resultActions = mockMvc.perform( get("/oauth/token"). accept(md). param("grant_type", "password"). param("client_id", "simple-client"). param("client_secret", "simple-client-secret-key"). param("username", "admin"). param("password", "admin")). andExpect(status.isOk). andExpect(content.contentType(md)). andExpect(jsonPath("$.access_token").exists). andExpect(jsonPath("$.token_type").exists). andExpect(jsonPath("$.expires_in").exists) val contentAsString = resultActions.andReturn.getResponse.getContentAsString val map: Map[String, String] = objectMapper.readValue(contentAsString, new TypeReference[Map[String, String]] {}) val access_token = map.get("access_token").get val token_type = map.get("token_type").get mockMvc.perform( get("/api/auth/check"). accept(md). header("Authorization", token_type + " " + access_token)). andExpect(status.isOk). andExpect(content.contentType(md)). andExpect(jsonPath("$.username").value("admin")). andExpect(jsonPath("$.enabled").value(true)) }
ãããŠããã©ãŒã ãä»ããæ¿èªã®ãã¹ã
it should "Login as admin through user form with default password" in { mockMvc.perform( post("/auth/j_spring_security_check"). contentType(MediaType.APPLICATION_FORM_URLENCODED). param("j_username", "admin"). param("j_password", "admin")). andExpect(status.is3xxRedirection()). andExpect(header().string("location", "/index.html")) }
ä»ã®ãšããããã§åæ¢ããŸãã æ¬¡ã®èšäºã§ã¯ãSASSãCoffeeScriptãæå°åããã³ãã®ä»ã®äŸ¿å©ãªãã®ã®ã¢ã»ã³ããªã䜿çšããŠãæåç·ãäœæããŸãã
Yeoman ã
Bower ã
Gruntãšåéã«ãªãã
VagrantãéããŠããã°ã©ããŒåãã®ç°å¢ãå±éããŸãã
ãããã¯ãã¹ãŠãBitbucket
https://bitbucket.org/andy-inc/scala-habr-templateã§è¡šç€ºã§ã
ãŸã ã
ã¿ã€ããã¹ãééããèŠã€ããå Žåã¯ãPMã«é£çµ¡ããŠãã ããã ããããããäºæ¿ãã ããã
ãéèŽããããšãããããŸãããæèŠãå
±æããŠãã ããã