Goalist Developers Blog

デザインあ展 in Tokyo に行ってきた

f:id:watabe1028:20180803123133j:plain
こんちは。
今回はイベントレポートです。
もちろん、ただの遊びです。

突然ですが、みなさん「デザインあ」をご存知ですか?
www.nhk.or.jp

Eテレヘビーユーザーの自分は「ピタゴラスイッチ」と「デザインあ」が大好きです。
そんな「デザインあ展 in Tokyo」に行ってきました。
久しぶりのレポ記事です。
ネタバレ注意です。自己責任で読んでください。
 
 

GAIYO

デザインあ展 in Tokyo
場所:日本科学未来館
期間:2018/7/19 〜 2018/10/18
時間:10 〜 17時
料金:1,600円(大人)
詳細:
www.design-ah-exhibition.jp

平日の11時ごろ行ったのですが、忘れていました。
世は夏休みだと言うことを。
元気の良い少年達が縦横無尽にはしゃいでいました・・・
その辺はお気をつけて。

早速レポ!
全部載せたいところですが面倒なので一部だけ紹介します!
 
 

TAMAGO!

f:id:watabe1028:20180803123933j:plain
見ての通り、たまごです。焼いたり茹でたり。
デザイン的な発想よりも食玩技術に驚きです。
 
 

TSUMIKI!

f:id:watabe1028:20180803123958j:plain
食器の形をした積み木のようなもの。
もくもくと木を積み上げていた少年に
「崩した方が負けね」と勝負を挑み、完勝してその場をさりました。
しょうもない大人ですね。
 
 

NANIWO?

f:id:watabe1028:20180803124035j:plain
何をやっているでしょうか?
多分けん玉です。(本当に多分)
(この膝の効かせ具合!)

f:id:watabe1028:20180803124644j:plain
これは傘です。(これも多分)
なるほどねーってなります。
 
 

MOSYA!

f:id:watabe1028:20180803124725j:plain
これは街にあるフォントを模写していました。
フォント大好きなので(最近は筑紫A丸ゴシック推し)上がりました。
 
 

MO、MOJI?

f:id:watabe1028:20180803124806j:plain
おわかりいただけただろうか?

わかりにくいのでアップを。
f:id:watabe1028:20180803124832j:plain
まだわかりにくいですかね?

f:id:watabe1028:20180803124900j:plain
も、もみあげ!

これらは全てテキストにされていました。
これすごい!
実際に見て欲しい!
本当に全部テキストになってる!!
 
 

SAIGONI・・・

この記事では全体の5分の1も紹介出来ていません。
また、写真よりも実際に見た方が絶対面白いです。
値段も1,600円(大人)と高くないので是非見に行ってください。
だいたい2時間くらいで回れるのでお台場でショッピング前なんかにいいですね。
10月までやってます!

最後に説明が面倒だったので書くのをやめた写真をいくつかあげておきます。

f:id:watabe1028:20180803125018j:plain
f:id:watabe1028:20180803125050j:plain
f:id:watabe1028:20180803125122j:plain
f:id:watabe1028:20180803125147j:plain

DockerでMySQL8環境をつくるよ

ローカル環境に影響なしでMySQL8を試してみてエだろ…"お気軽"にヨ…

Docker for Macをインストール

www.docker.com

$ brew update
$ brew cask install docker

VirtualBoxのこととかは考えなくてよい

Docker for Macだと
VirtualBox上にDockerホストとなるマシンを自分で作成とかなんもしないでも
デフォルトでよしなにやってくれるのだそうで(HyperKitというのでLinux VMがつくられる、軽量)
これに明確に分かるまで激しく遠回りしてしまった
最初から公式ドキュメント読んでいればそれでよかったんだ
Docker Toolbox + VirtualBoxのブログ記事がおおくて…惑わされた…

公式のMySQLイメージを入手

https://hub.docker.com/_/mysql/

$ docker pull mysql:latest
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mysql               latest              3da0f4853002        36 hours ago        445MB

ウム

MySQLイメージからコンテナの作成と起動

$ docker run --name mysql8.0.12 -e MYSQL_ROOT_PASSWORD=xxxxx -p 3307:3306 -d mysql:latest

ローカルのクライアント側からは3307ポートでアクセスしようと思う

起動中のプロセスリストを見てみる

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
7712194c222b        mysql:latest        "docker-entrypoint.s…"   7 seconds ago       Up 7 seconds        0.0.0.0:3307->3306/tcp   mysql8.0.12

ウム

このコンテナに接続して中身を確認してみる

$ docker exec -it 7712194c222b bash

root@7712194c222b:/# mysql --version
mysql  Ver 8.0.12 for Linux on x86_64 (MySQL Community Server - GPL)

ちゃんと8が入っている〜

ユーザ作成

root@7712194c222b:/# mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 14
Server version: 8.0.12 MySQL Community Server - GPL

mysql> USE mysql;

mysql> CREATE USER user1 IDENTIFIED BY 'yyyyy';

mysql> CREATE DATABASE db_name;

mysql> GRANT ALL PRIVILEGES ON db_name.* TO 'user1'@'%';

ローカルのMySQLクライアントからコンテナのDBに接続

$ mysql -u user1 -p --port 3307
Enter password: 
ERROR 1045 (28000): Access denied for user 'user1'@'localhost' (using password: YES)

できね〜

$ mysql -u user1 -p -h localhost --port 3307
Enter password: 
ERROR 1045 (28000): Access denied for user 'user1'@'localhost' (using password: YES)

できね〜

www.bunkei-programmer.net

ホストは127.0.0.1で指定でどうか

$ mysql -u user1 -p -h 127.0.0.1 --port 3307
Enter password: 
ERROR 2059 (HY000): Authentication plugin 'caching_sha2_password' cannot be loaded: dlopen(/usr/local/Cellar/mysql/5.7.21/lib/plugin/caching_sha2_password.so, 2): image not found

ヌ"ア"ア"

qiita.com

パスワードの認証方法が変わっているのだね

外部からのパスワード認証で接続可能なユーザーを作成する

Dockerコンテナ内に入って

$ docker exec -it 7712194c222b bash

mysql_native_passwordで認証するユーザー作成

root@7712194c222b:/# mysql -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 14
Server version: 8.0.12 MySQL Community Server - GPL

mysql> CREATE USER nativepassuser IDENTIFIED WITH mysql_native_password BY 'yyyyy';

mysql> SELECT user, host,  plugin, authentication_string FROM user;
+------------------+-----------+-----------------------+------------------------------------------------------------------------+
| user             | host      | plugin                | authentication_string                                                  |
+------------------+-----------+-----------------------+------------------------------------------------------------------------+
| nativepassuser   | %         | mysql_native_password | *D7D00ED2D87ECF8666F08E30C4A6EB61AB9982FC                              |
| user1            | %         | caching_sha2_password | $A$005$~j\'SF)I2)
oVUJMUxJKcEdZom3QAppyIBBX0vu5Rh/fr.g.fbw7YMe15 |
| mysql.infoschema | localhost | caching_sha2_password | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED |
| mysql.session    | localhost | caching_sha2_password | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED |
| mysql.sys        | localhost | caching_sha2_password | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED |
/hN>ot             | localhost | caching_sha2_password | $A$005$NrwN
    &r(:NjHjf693xvJ9AQzt5F9J4D885VzpXnduNOLcJ.TcoYc4 |
+------------------+-----------+-----------------------+------------------------------------------------------------------------+
6 rows in set (0.00 sec)

再度ローカルのクライアントからコンテナのDBに接続を試す…

$ mysql -u nativepassuser -p -h 127.0.0.1 --port 3307
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 8.0.12 MySQL Community Server - GPL

いけました!!!!ヴォエ!!!!
これでやりたい放題できますね

感想

お気軽ではなかったが勉強にはなった!ポジティブバカの言葉です

Let's { Dart } before we { Flutter }



The above program was written in Dart and it shouldn't come to you as a surprise if you are from Java or C programming background. (above lines are editable so go ahead and replace my name with yours and press play button ▶️)

So, what is Dart???
An overview of Dart on Ecma website reads as

Dart is a class-based, single-inheritance, pure object-oriented programming language. Dart is optionally typed and supports reified generics.

If that thing was steaming pile of mumbo-jumbo to you then allow me to explain in my subsequent blog named "Getting Started with Dart".

The greatest innovations are the ones we take for granted, like light bulbs, refrigeration, and penicillin.

However; in a world where the miraculous very quickly becomes common-place, how can a company, especially one as big as Google, maintain a spirit of innovation year after year? 🤔

While you are thinking about it... eureka!! there is already something new in the market and you gotta learn it, because...
Once a new technology rolls over you, if you're not part of the steamroller, you're part of the road.
With no surprise, Google this year came with the complete set of frameworks for all your needs, with its own programming language {Dart}.

1) Web-Applications (AngularDart)

AngularDart is a web app framework that focuses on productivity, performance, and stability.

2) Android and iOS Applications (Flutter)

Flutter is Google’s mobile app SDK for crafting high-quality native interfaces on iOS and Android in record time using only single code base.


So let's have a tour of these things together...

f:id:vivek081166:20180723142405p:plain


See you in the subsequent blog post with

"Getting Started with Dart"

till then happy learning 😊

VAPIDを用いたデスクトップ通知のAngular6とJavaによる実装

こんにちは。
新卒の増田です。

今回、クライアント(フロントエンド)にAngular6、サーバー(バックエンド)にPlayFramework(Java)を使ってデスクトップ通知を実装しようとした際、あまり参考になる資料がなかったのでここに残しておきます。

VAPIDやPush通知に関する細かい仕組みについては他のサイト(ここなど)を参照していただいて、で、実際に何を書けば良いのかにだけ焦点を当てて紹介していきます。

僕はエンジニア新生児なので、初めから書いていきます。
何もないところからデスクトップ通知ができるように。
もう本当に初めの方から。
ただ、sbtやAngular CLIなどのツールはこっそり入っているという前提です。

使っているもの

  • PlayFramework 2.6
  • Java 1.8
  • Eclipse 4.7
  • Angular6
  • TypeScript
  • WebStorm

ディレクトリとプロジェクトの作成

まず、適当なところにフォルダを作ります。名前も適当に。

$ mkdir notification
$ cd notification

そしてそこにクラアイアント(Angular)とサーバー(PlayFramework)、それぞれのプロジェクトを作成します。コマンドで。
まずはサーバー側。

notification$ sbt new playframework/play-java-seed.g8

name [play-java-seed]: backend
organization [com.example]: 
scala_version [2.12.6]: 
play_version [2.6.16]: 
sbt_version [1.1.6]: 

色々聞かれますが、とりあえず名前だけbackendと付けてあげてあとはEnterキーです。
もちろん名前もなくて良いです。お好みで。

次はクライアント側。

notification$ ng new frontend

こちらも名前はお好みで。
さて、これでとりあえずはディレクトリができました。いえい。

サーバー側の準備

サーバー側から触っていくことにします。
というわけで何も考えず

notification$ cd backend

そして今回はEclipseを使っていくので、project/plugins.sbtに

addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")

を書き加え、ついでに今回必要なものは全てありがたいライブラリを使わせていただくので、build.sbtにあれこれ追記します。

libraryDependencies ++= Seq(
  filters,
  "org.bouncycastle" % "bcprov-jdk15on" % "1.54",
  "nl.martijndwars" % "web-push" % "3.1.0",
  "commons-io" % "commons-io" % "2.4"
)

はい。
ここで、plugins.sbtやbuild.sbtに追加したものをEclipseに適用するために

backend$ sbt eclipse

をしておきます。
次は、サーバーとクライアントで通信をするのですが、このままだとクライアントからサーバーにリクエストを送ってもCORSで弾かれてしまいます。CORSについては割愛します。僕もよく知りません。

とりあえず、application.confに

play.filters {
  ## CORS filter configuration
  # https://www.playframework.com/documentation/latest/CorsFilter
  # ~~~~~
  # CORS is a protocol that allows web applications to make requests from the browser
  # across different domains.
  # NOTE: You MUST apply the CORS configuration before the CSRF filter, as CSRF has
  # dependencies on CORS settings.
  enabled += "play.filters.cors.CORSFilter"
  cors {
    # Filter paths by a whitelist of path prefixes
    pathPrefixes = ["/"]

    # The allowed origins. If null, all origins are allowed.
    allowedOrigins = null

    # The allowed HTTP methods. If null, all methods are allowed
    allowedHttpMethods = null
    allowedHttpHeaders = null

    #preflightMaxAge = 3 days
  }
}

と書き加えた上で(コメント部分はもちろんなくても)、appの下にFilters.javaというクラスを作成します。Eclipseで普通にクラスを作成するとデフォルトパッケージの下に来ますが気にしません。
で、Filters.javaに

import javax.inject.Inject;

import play.filters.cors.CORSFilter;
import play.http.DefaultHttpFilters;
import play.mvc.EssentialFilter;

public class Filters extends DefaultHttpFilters {

    CORSFilter corsFilter;

    @Inject
    public Filters(CORSFilter corsFilter) {
        super(corsFilter);
        this.corsFilter = corsFilter;
       }

    public EssentialFilter[] filters() {
        return new EssentialFilter[] { corsFilter.asJava() };
    }
}

というのを貼り付けましょう。
PlayFrameworkのHPからサンプルをダウンロードすると初めからあるような気がするので、
そこからコピペしても良いと思います。これでCORSで弾かれなくなりました。幸せ。

ここまででサーバー側の設定はおしまいです。

クライアント側の準備

次はクライアント側の準備に移ります。
とりあえずfrontendディレクトリに移って、まずはこのプロジェクトをPWA化しましょう。
デスクトップ通知にはサービスワーカーが欠かせないらしいので、そのためです。
サービスワーカーが何であるのかについては世に遍在している説明を発見していただければ。

frontend$ ng add @angular/pwa —project frontend
frontend$ npm i @angular/service-worker --save -D

クライアント側の設定はこれでおしまいです。Angular CLIさんが必要な記述も全部してくれていますので。ちなみに ここなどを参考にしています。 参考ページではindex.htmlとmanifest.jsonを修正していますが、必要ありませんでした。

というわけで、これでサービスワーカーを使えるようになりました。

ここまでで必要な準備が終わったのでやっと本題に入れます。
デスクトップ通知の実装へ。

サーバー側でECDSAによる鍵ペアを生成

鍵ペアの生成には初めに準備しておいたライブラリを使わせていただきます。
ECDSAというのがどういうものなのかよく知りません。
知りませんが、それでも使えるというのが大事ということで。

鍵ペアですが、実は毎回作成する必要はなく、アルゴリズムに従って生成された鍵ペアさえあれば良いので、一度作ったものをそのままペタッ。

とりあえず一度生成すべく、Eclipseでapp下に何か適当なメインクラスを作成します。
本当はパッケージなど分けた方が良いのでしょうが、そういう細かいことは必要に迫られたときに。
例えば表示するだけならこのように、VapidKey.javaのようなクラスを作成します。

import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import nl.martijndwars.webpush.cli.commands.GenerateKeyCommand;
import nl.martijndwars.webpush.cli.handlers.GenerateKeyHandler;

public class VapidKey {

    public static void main(String[] args) {
        try {
            new VapidKey().generateKeys();
        } catch (Throwable th) {
            th.printStackTrace();
        }

    }

    public void generateKeys() throws Throwable {
        Security.addProvider(new BouncyCastleProvider());
        GenerateKeyHandler gh = new GenerateKeyHandler(new GenerateKeyCommand());
        gh.run();
    }
}

全部mainに入れれば良いじゃんと思いつつ。
ただ、何もせずにこれを実行すると、たぶん「メインクラスが見つかりません」などと言われます。
なので、

backend$ sbt compile

をして、プロジェクトをインポートし直し、ここにあるように、ビルドパスを設定します。

  • Eclipse上でプロジェクトを右クリック > Build Path > Configure Build Path...
  • LibraryタブからAdd Class Folderを選択
  • 開いたダイアログでtarget/scala-2.12/classesにチェックを入れてOK

そしたら実行できるようになるはずです。
実行できました。できたらコンソールに

PublicKey:
BP198Ghlpaac41UMKrRvYyNt56tt7JCcgusX1JSh1e3W-kApskF2BLqCNi0c2GBjPx5BflPezwvi0OwVsaGTclI=
PrivateKey:
Kzh4c_SbGLXoybns7pCiRdvhx9b0m_H5AEyD8DD9Bvw=

のように出力されるのではないかと。

繰り返しになりますが、アルゴリズムに従って生成された鍵ペアさえあれば良いので、上記のものをそのまま利用してもらっても大丈夫なはずです(なのでこのクラスを作らずに上の鍵を使うのでも良いような)。セキュリティ的にどうなのかというのはひとまずおいといて。

生成したサーバー鍵をクライアント側に送信

クライアントからリクエストを送って、そのレスポンスでサーバー鍵を渡します。
HomeController.javaに

import play.libs.Json;

public Result vapidKey() {
     // さっきの鍵をそのままペタッ
     String serverKey = "BP198Ghlpaac41UMKrRvYyNt56tt7JCcgusX1JSh1e3W-kApskF2BLqCNi0c2GBjPx5BflPezwvi0OwVsaGTclI=";
     // サーバーキーを返すだけ
     return ok(Json.toJson(serverKey);
}

というように追記した上で、routesに

GET /vapidKey        controllers.HomeController.vapidKey

としておくことで、この/vapidKeyにリクエストが送られたときにサーバーの公開鍵を返すようになります。
普通です。

プッシュサーバーにサーバー鍵を登録

subscriptionからエンドポイントやクライアントの公開鍵等を取得

取得した諸々の情報をサーバー側へ送信

クライアントでPush通知を受け取った際にデスクトップ通知として表示

4つまとめてやります。

app.component.tsを

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { SwPush } from '@angular/service-worker';

@Componetn(中略)

export class AppComponent implements OnInit {

    constructor(private swPush: SwPush, private http: HttpClient) {

        // Push通知が送られて来たときに受け取るやつ
        this.swPush.messages.subscribe(
            (message) => {

                const options = {
                    body:  message['message'],
                    // trueにするとユーザーから消されるるまで表示し続けられます。
                    requireInteraction: true,
                    // tag名が異なると異なる通知として出され(下にずらっと並ぶ)、
                    // tag名が同じものはそれ以前からある通知に上書きされるようです。
                    tag: 'notification_tag',
                    // これが何かは忘れてしまいました
                    renotify: false,
                };

                // どうしてかよく分かりませんが、Notificationをnewすると表示されるみたいです。
                const n = new Notification(message['title'], options);

            });
    }

    ngOnInit() {
        this.setDesktopNotification();
    }

    setDesktopNotification(): void {
        // サーバー公開鍵を受け取るべくリクエストを送信
        this.http.get('http://localhost:9000/vapidKey')
            .subscribe(
                // レスポンスに鍵が入っているはずなので、それをserverPublicKeyにセット
                (res) => {
                    this.swPush.requestSubscription({
                        serverPublicKey: res.toString()
                    }).then(
                        (pushSubscription: PushSubscription) => {

                            // 必要なものは全てpushSubscriptionに入っているので、アクセスすると取れます。
                            // endpointとp256dhとauthを取得
                            const endpoint = pushSubscription.endpoint;
                            const p256dh: string = btoa(String.fromCharCode.apply(null, new Uint8Array(pushSubscription.getKey('p256dh'))));
                            const auth: string = btoa(String.fromCharCode.apply(null, new Uint8Array(pushSubscription.getKey('auth'))));

                            const body = {
                                       endpoint: endpoint,
                                       p256dh: p256dh,
                                       auth: auth
                            };

                            // 送り先は/webPushという場所にしました。
                            this.http.post('http://localhost:9000/webPush', body).subscribe(
                                (res) => {
                                    // console.log('[App] Add subscriber request answer', res);
                                 },
                                 (err) => {
                                     console.log(err);
                                 });
                        }).catch(
                            err => console.log(err)
                        );
                },
                (err) => {
                    console.log(err);
                });
    }

}

として、
あとComponentでHttpClientを使うために、app.module.tsにちろっと追記しておきます。

import { HttpClientModule } from '@angular/common/http';

@NgModule({
    (中略)
    imports: [
        ...
        HttpClientModule,
        ...
    ],
    ()
})

サーバー側からメッセージをエンドポイントへと送ると、
プッシュサーバーがクライアント側にPush通知を送ってくれます。
あとはそれを受け取ってあれこれするとデスクトップ通知ができるのです。きっと。ここでそのプッシュサーバーからの通知を受け取る役目を負っているのが、constructorの中のthis.swPush.messages.subscribe()です。

ちなみに、Notificationのオプションはここに載っているものが使えるのではないかと勝手に思っています。「Instance properties」のところですね。

一つ引っかかったところがありまして、pushSubscriptionに入っているならそれをそのまま返せば良いじゃないかと初めはなったのですが、そのまま返すとなぜか文字が欠けてしまったりします(この理由がよく分かってません)。
なので、文字列に変換してから必要な分だけを渡しています。

というわけで、送信できるようになりました。

クライアント側から送られてくるリクエストを受け付ける窓口作成

当然のことながら、上記でクライアントから送った情報を受け取る窓口が必要になります。
さっきと同じようにして、HomeController.javaに

import play.mvc.Http.Request;

public Result webPush() {

        Request req = request();

        // ここの受け取り方はクライアントからどう送るかによって色々です。たぶん。
        JsonNode jn = req.body().asJson();
        String endpoint = jn.get("endpoint").asText();
        String p256dh = jn.get("p256dh").asText();
        String auth = jn.get("auth").asText();

        System.out.println("endpoint : " + endpoint);
        System.out.println("p256dh : " + p256dh);
        System.out.println("auth : " + auth);

        return ok(Json.toJson("OK!"));
}

と追記し、そしてroutesに、

POST /webPush        controllers.HomeController.webPush

というような感じで追記しておくと、クライアントから送られて来た情報がコンソールに出力されると思います。

ここまで来たら次はサーバーとクライアントで通信させる必要があるので、サーバーとクライアントをそれぞれbuildします。
まずはサーバー側を

backend$ sbt run

おしまい。

次はクライアント側

frontend$ ng build --prod
frontend$ cd dist/frontend
frontend$ http-server

突然出て来たhttp-serverというのは、ありがたい記事によると

このhttp-serverをインストールしておくと、任意のディレクトリでhttp-serverというコマンドを実行するだけでそのディレクトリをドキュメントルートにしたウェブサーバーが起動します。

というものらしいです。とりあえず

$ npm install -g http-server

をしておけば良いのではないでしょうか。あって困るものではないでしょうから。
そしてあとはlocalhost:8080をブラウザで開くと、通信が行われて、サーバーのコンソールに

endpoint : https://fcm.googleapis.com/fcm/send/eit1VHpri4g:APA91bFWD6MZw-ulAbKRlIfSyqUvRYGqcPfUFmxqkj4bWmUzUaBB-6OZyUmvy9Cob6WYHJH_JpWeU-C_nPsDO7yiMEqPXg6DXD3KXKiS85BgbKgQxcuD15_LK3s09yxeiqKcCrx6IUasFfBq0oQ6nt5b0SxiPXX4ug
p256dh : BOeKRtnhXWm+dSVAHP3ws7EE6cYUi83Lb6ny7+IweHniUSywQAc9BzZmVtVEyY5SKGxR4QGG6PAWNzlr5LhadA8=
auth : 4rIMQM5HTe04kclcRU2Rhw==

というように出力されるのではないかと(上手くいかなかったらリロードなどを)。
ちなみに、サーバーの鍵ペアはここに載せてあるものでも使えますが、エンドポイント等はここにあるものをコピペしてもたぶん送信できませんのでお気をつけて。

大事なことを一つ。あとでPush通知を飛ばしたときに受け取らないといけないので、フロント側は動かしたままに。

エンドポイントへとメッセージを送信

これも先のライブラリを使わせていただくと、 とっても簡単に送信できてしまいます。便利。
適当にapp下にWebPush.java(名前は適当に)をこんな感じで作成し、

import java.security.Security;

import org.apache.http.HttpResponse;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import com.google.gson.JsonObject;

import nl.martijndwars.webpush.Notification;
import nl.martijndwars.webpush.PushService;
import nl.martijndwars.webpush.Utils;

public class WebPush {

    public static void main(String[] args) {
            try {
                new WebPush().exec();
            } catch (Throwable th) {
                th.printStackTrace();
            }
    }

    public void exec() throws Throwable {

            Security.addProvider(new BouncyCastleProvider());

            // jsonから取得と書いていますが、出力されたものをコピペです
            String endpoint = /* webPushに送られてくるjsonから取得(上記のendpointに相当) */;
            String userPublicKey = /* webPushに送られてくるjsonから取得(上記のp256dhに相当) */;
            String userAuth = /* webPushに送られてくるjsonから取得(上記のauthに相当) */;

            // Base64 string server public/private key
            String vapidPublicKey = /* 初めに作ったサーバーの公開鍵 */;
            String vapidPrivateKey = /* 初めに作ったサーバーの秘密鍵 */;

            // Construct notification
            Notification notification = new Notification(endpoint, userPublicKey, userAuth, getPayload());

            // Construct push service
            PushService pushService = new PushService();
            pushService.setSubject("mailto:admin@martijndwars.nl");
            pushService.setPublicKey(Utils.loadPublicKey(vapidPublicKey));
            pushService.setPrivateKey(Utils.loadPrivateKey(vapidPrivateKey));

            // Send notification!
            HttpResponse httpResponse = pushService.send(notification);

    }

    private byte[] getPayload() {
            JsonObject jsonObject = new JsonObject();
            jsonObject.addProperty("title", "おっはー");
            jsonObject.addProperty("message", "World");

            return jsonObject.toString().getBytes();
    }
}

これを実行すると、クライアントへとPush通知が飛ぶようになります。
別のメインクラスが実行されたりそもそも実行されなかったりするときは(たぶん普通には実行できないように思うのですが)、sbt compileをしてEclipseでインポートし直すなどしてみてください。それで実行できるのではないかと思います。もっと良い方法があるように思いつつ。

ここまでで必要な準備は終わったので、あとはさっき作ったWebPush.javaを実行してメッセージを送ってあげればデスクトップ通知ができるのではないでしょうか。たぶん。
というわけで実行しましょう!

f:id:s-masuda:20180719203906p:plain でけた!!!!

めでたしめでたし。

というわけでデスクトップ通知の大枠はできたのではないかと思いますので、あとは煮るなり焼くなり炙るなりすればきっと素晴らしいデスクトップ通知が。

こんな感じで、ほぼ入門程度の技術研修(それもJavaだけ!!)を終えたばかりの新卒なのに「AngularとPlay使ってデスクトップ通知実装してね!」などと突然言われてしまうような会社ではありますが、僕みたいな初心者でもこうしてちゃんと実装しきるところまで助けてもらえますので、もしよければどうぞ。そしてぜひ大阪に。

fresh-recruiting.goalist.co.jp

お待ちしております。

Swaggerを使ってRestful APIを記述する

こんにちは、ゴーリストのトゥアンです。

ウェブ開発でRestful APIはだんだん普及していると共に、Resful APIを記述するためにいろいろな仕方があります。

Swaggerを使うことは下記の通りに、いくつかの利点があります。

  • Amazon API Gatewayへ展開できる
  • フロントエンド開発のため、モックサーバーを作りやすい
  • OpenAPI仕様の基準に基づくAPIの入力(リクエスト)・出力(レスポンス)を定義できる

Swagger 3.0 は2017年07月にリリースされましたが、現在AWSはSwagger 2.0だけサポートしているので、今回の記事でSwagger 2.0について説明します。

Swaggerファイルのフォーマット

SwaggerでサポートしているフォーマットはYAMLとJSONです。

Swaggerファイルの構造は複数階層のオブジェクトです。ルートオブジェクトは「Swagger」というオブジェクトです。

1. Swagger オブジェクト

詳しい情報は下記のリンクにご参考ください。

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object

大切なフィールドは:

フィールド名 タイプ 説明
swagger String 必須です。"2.0"という固定な価値があります。
info Object 必須です。情報のオブジェクトです。後で説明します。
host String ホスト名又はホストのIPアドレスです。
basePath String 全部APIの共有なパスです
schemes String 全部APIのプロトコル(httpやhttps, ws, wssなど)です
paths Object 必須です。
全部APIを一つずつ記述するためのオブジェクトです。
後で説明します。
definitions Object 自由なオブジェクトを定義できます。後で説明します。

2. info オブジェクト

APIについてのメタデータです。 詳しい情報は下記のリンクにご参考ください。 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#info-object

大切なフィールドは:

フィールド名 タイプ 説明
title String 必須です。アプリケーションのタイトルです
version String 必須です。API記述仕様書のバーションです。

3. paths オブジェクト

APIの記述情報です。 詳しい情報は下記のリンクにご参考ください。 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#paths-object

大切なフィールドは:

フィールド名 タイプ 説明
/{path}
{path}は任意に定義できる
Object 定義されたpathに該当するAPIの記述です。
「Path Item」 というオブジェクトです。
後で説明します。

Path Itemオブジェクトの詳しい情報は下記のリンクにご参考ください。 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#paths-item-object

大切なフィールドは:

フィールド名 タイプ 説明
get Object 定義されたpathに該当するGETメソードの記述です。
「Operation」というオブジェクトです。後で説明します。
put Object pathに該当するPUTメソードの記述です。
タイプはgetのOperationオブジェクトと同じです。
post Object pathに該当するPOSTメソードの記述です。
タイプはgetのOperationオブジェクトと同じです。
delete Object 定義されたpathに該当するDELETEメソードの記述です。
タイプはgetのOperationオブジェクトと同じです。
options Object pathに該当するOPTIONSメソードの記述です。
タイプはgetのOperationオブジェクトと同じです。
head Object pathに該当するHEADメソードの記述です。
タイプはgetのOperationオブジェクトと同じです。
patch Object pathに該当するPATCHメソードの記述です。
タイプはgetのOperationオブジェクトと同じです。
parameters Array 全部のメソッドの共有なリクエストパラメーターです。
配列要素のタイプは以下のいずれかです。
・definitionsに自由に定義したオブジェクトに参考
・「Parameter」というオブジェクト。後で説明します。

4. operation オブジェクト

定義されたパスの一つのメソッドのリクエスト・レスポンスの記述です。
詳しい情報は下記のリンクにご参考ください。

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object

大切なフィールドは:

フィールド名 タイプ 説明
parameters Array メソードのリクエストパラメーターです。
配列要素のタイプは以下のいずれかです。
・definitionsに自由に定義したオブジェクトに参考
・「Parameter」というオブジェクト
responses Object レスポンスを記述するためのオブジェクトです。
後で説明します。

5. parameter オブジェクト

リクエストパラメーターの記述です。
詳しい情報は下記のリンクにご参考ください。

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#parameter-object

大切なフィールドは:

フィールド名 タイプ 説明
name String 必須です。パラメーターの名前です。
in String 必須です。リクエストにパラメーターを含める場所です。
価値は以下のいずれかです。
・"query"
・"header"
・"path"
・"formData"
・"body"
required Boolean 定義されたパラメーターは必須かどうか記述です。

6. responses オブジェクト

APIレスポンスの記述です。 詳しい情報は下記のリンクにご参考ください。 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#responses-object

大切なフィールドは:

フィールド名 タイプ 説明
{HTTP ステータスコード}
(400や404, 500など)
Object 定義されたHTTPステータスコードに該当するレスポンスの記述です。
タイプは以下のいずれかです。
・definitionsに自由に定義したオブジェクトに参考 
・「Response」 というオブジェクトです。後で説明します。

Responseオブジェクトの詳しい情報は下記のリンクにご参考ください。 https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#responseObject

大切なフィールドは:

フィールド名 タイプ 説明
description String 必須です。レスポンスの概念記述です。
schema Object APIレスポンスの構造詳細記述です。後で説明します。

7. definitions オブジェクト

自由なオベジェクトの定義です。
詳しい情報は下記のリンクにご参考ください。

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#definitionsObject

definitions オブジェクトのフィールドは:

フィールド名 タイプ 説明
{name}
name: 定義したいオベジェクトの名前です。
Object 指定されたnameがあるオブジェクトの記述です。
タイプはresponseのschemaオブジェクトと同じです。
後で説明します。

SchemaオブジェクトはJSON Schema仕様書に基づいて定義されます。
JSON Schema仕様書は下記のリンクにご参考ください。
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#definitionsObject 
また、JSON Schema仕様書の他にSwaggerのSchemaオブジェクトは特別なフィールドもあります。
詳しい情報は下記のリンクにご参考ください。

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#schema-object

大切なフィールドは:

フィールド名 タイプ 説明
type String 定義したいオブジェクトのタイプです。
サポートしているタイプは:
原始タイプ("integer"や"string"など)、
"array"と"object"です

typeが"array"である場合、下記のフィルードがあります。

フィールド名 タイプ 説明
items Object 配列要素を定義するためのSchemaオブジェクトです。

typeが"object"である場合、下記のフィルードがあります。

フィールド名 タイプ 説明
required Array 必須なフィルード名の配列です。
properties Object オブジェクトのフィルードを定義するための各Schema オブジェクトです。

サンプル

課題:https://example.swagger.io/v1/users/{user_id} パスのGETメソッドとPUTメソッドを記述します。

GETメソッド:
リクエストのパラーメータ:
+ user_id : パスにある
レスポンスのオブジェクト:ユーザの名前とユーザのメールアドレス

PUTメソッド:
リクエストのパラーメータ:
+ user_id : パスにある
+ name: ボディにある
+ email: ボディにある
レスポンスのオブジェクト:成功な場合に200コードを返却して、失敗な場合に400コードと404コードを返却します。

上記のAPIを記述するために、下記のSwaggerファイルを作ります。

swagger: "2.0"
info: # infoオブジェクト
  title: "Swaggerの例"
  version: "1.0.0"
host: "example.swagger.io"
basePath: "/v1"
schemes:
- "https"
paths: # pathsオブジェクト
  /user/{user_id}:
    get: # getメソッドのoperationオブジェクト
      tags:
      - "userAPI"
      summary: "ユーザーIDによって、ユーザー情報を取得する"
      operationId: "getUserByUserId"
      produces:
      - "application/json"
      parameters: # parameterオブジェクトの配列
      - name: "user_id"
        in: "path"
        description: "ユーザーID"
        required: true
        type: "number"
      responses: # responsesオブジェクト
        200:
          description: "成功なレスポンス"
          schema: # schemaオブジェクト
            $ref: "#/definitions/User" 
            # definitionsに定義されたオブジェクトの参考
        400:
          description: "無効なユーザーID"
        404:
          description: "ユーザーを見つけていません"
    put: # putメソッドのoperationオブジェクト
      tags:
      - "userAPI"
      summary: "ユーザーIDによって、ユーザー情報を更新する"
      operationId: "updateUserByUserId"
      produces:
      - "application/json"
      parameters: # parameterオブジェクトの配列
      - name: "user_id"
        in: "path"
        description: "ユーザーID"
        required: true
        type: "number"
      - in: "body"
        name: "body"
        required: true
        schema: # schemaオブジェクト
          $ref: "#/definitions/UpdateUserReq" 
          # definitionsに定義されたオベジェクの参考
      responses: # responsesオブジェクト
        200:
          description: "成功なレスポンス"
        400:
          description: "無効なユーザーID"
        404:
          description: "ユーザーを見つけていません"
definitions: # definitionsオブジェクト
  User: # schemaオブジェクト
    type: "object"
    properties:
      id: # schemaオブジェクト
        type: "integer"
        format: "int64"
      name: # schemaオブジェクト
        type: "string"
      email: # schemaオブジェクト
        type: "string"
  UpdateUserReq: # schemaオブジェクト
    type: "object"
    required:
      - "name"
      - "email"
    properties:
      name: # schemaオブジェクト
        type: "string"
      email: # schemaオブジェクト
        type: "string"

Swagger UI」を使って、記述したAPIは綺麗に表示できます。

API概要の表示 f:id:t-nguyen:20180709155906p:plain GETメソッドの表示 f:id:t-nguyen:20180709154630p:plain PUTメソッドの表示 f:id:t-nguyen:20180709154752p:plain

ツール

Swaggerを書くために、「Swagger Editor」を使って、エラーがある場合、すぐ報告は出ます。

Swaggerファイルを作成した後で、「Swagger UI」を使って、記述したAPIは綺麗に表示できます。

この二つのツールをローカル環境にダウンロードすることと、ウェブブラウザーにオンラインで使うことと、どちらもできます。

参考書類

今回の記事でSwagger 2.0について少し説明しました。
詳しい情報は下記のリンクにご参考ください。

バーション2.0 : https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
バーション3.0 : https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md

【求人情報ビッグデータ】HR EXPO 2018出展しています

こんにちは、開発部の飯尾です。
昨日7月11日水曜日、タイトル通りHR EXPOに参加してきました!

www.hr-expo.jp

企業の人事担当者さま向けに、サービス紹介・商談を行うイベントです。
ゴーリストもHR Tech領域で自社サービスを紹介してきました。

日本中の求人情報ビッグデータから、採用を読み解くHRogシリーズ

f:id:y-iio:20180712120616p:plain

https://chart.hrog.net/chart.hrog.net

map.hrog.net

ブースの様子
自社メディアのキャラクター「ふろぐん」カラーのTシャツで目立つ…

f:id:y-iio:20180712113445j:plain

私は現在、HRogチャートHRogマップの開発・運用を担当しています。
ときおり既存ユーザーの営業訪問に同行することはありますが
ど新規の営業イベントに参加するのは初です!

あんまり力になれてるわけでは無いですが

  • 呼び込みスクリプトの考案
  • とりあえず声出して足を止めてもらう

などなど、自分にもできることをがんばりました〜
普段営業メンバーがさらっとサービス紹介していることのすごさを実感ですね。

とはいえ「アルバイト採用担当されてるんですか?私が作ったサービスでこういうのあるんです!よかったらデモ触っていきませんか!」の最高の流れが実現できたので
個人的には満足です、ガハハ


このHR EXPOは明日13日金曜日まで開催中です。
ぜひゴーリストブースへ遊びに来てください!
\カワイラシイふろぐんうちわも配ってます/

こういうお祭り的なイベントもオモチロイものですね。
窓の外に出ると己の姿もよく見えると思います。

技術ブログとはいえ、採用のためにやってるものなので
社内エンジニアがどんな仕事してるかとか会社の雰囲気もこんなかんじで紹介していきます💪('ω'💪)

Gitがやばい!(1/3)

最近初めての開発者が多いプロジェクトに参加しました、そのためgitの強さがよ〜くわかりました(やらかしながらね)。シンプルから複雑な動き:つまり、「自分一人でバージョ二ングだけのために使ってる」場合(シンプル)から「ヤベー、いろんな人がいるし、ギットからデプロイされてるし、何が起きてる?!」(複雑)な状況に便利なものを軽く通しておくと便利かなと思いました。(以外と長かったため、この記事には基本:Level 0 とLevel 1を書いて見ました)

https://assets-cdn.github.com/images/modules/open_graph/github-mark.png

Level 0: init, .gitignore, status, remote

まずは環境設定みたいなものですね。プロジェクトフォルダーに git initを書きますとそのフォルダーがレポジトリとして初めて使えるようになります。 最初の状態で何も「トラッキング」されてないです。

status

git statusは現在の状況を確認するためのコマンドです。 git status を打ちますと、そのフォルダーの内容が全部出て、”untracked files”としての一覧になってます。 f:id:c-pattamada:20180705160140p:plain

.gitignore

このファイルの中に、gitを無視して欲しいファイル・パスなどを書きます。特に、プロジェクのlibフォルダーのような、外部ライブラリーのファイルを普通はのぞいて欲しいために、そういうパスを入力すれば良いです。 また、個人エディタ用のフォルダー(.ideaなど)も含めた方がいいかと思います。 この場合は ‘fileToIgnore.txt’を無視しましょう、ご覧の通り、lsには見えますがgitの方がむししているのです。

f:id:c-pattamada:20180705160159p:plain

入力後、またgit statusを打つと、ちゃんと無視してると確認できます。

f:id:c-pattamada:20180705160213p:plain

remote

Gitが複数の人たちが同じプロジェクトに開発できるために作られてますのでオンライン上にも置かれてるケースが多いです。ローカルのgit repositoryをオンラインに繋ぐのに githubでrepositoryを作る必要があります。 これを行うために、githubでのrepositoryも存在する必要があります。以下の記事のstep 2の画像を参考にしてください)

Deploying an Angular app to GitHub Pages - Goalist Developers Blog

Repository のurl をコピーして、
git remote add origin <url>
のようにローカルのgitのremoteを設定できます(originがそのサーバーの名前になりますが、他の名前でも大丈夫です)。

Level 1: Add, Commit, push, checkout, reset/revert.

残りのファイルを「トラッキング」したいと思いますので一旦プロジェクトフォルダから git add . を打ちましょう、そして、git statusで…. 緑! (ファイル一個ずつもadd できます。git add <ファイル名>)

f:id:c-pattamada:20180705160239p:plain

現在の緑のファイルをcommit する準備が整ってる。

と言っても… commitとは?

Commitはギットの基本技です。「add」されたファイルの状況を保存するようなことです。まさにゲームでボスの前に保存してから、ボスを倒した後でまた保存するように使うイメージです。何かがうまく行かなかった場合に、ボスの前のコミットに戻せます。

Commitするとどういう情報が保存されてるでしょう? それは以前のコミットと比べて、変更したファイル(何行目を変更したなど)の情報です。後に現れる「Merge」でこれが重要となります

Commitするために: git commit -m "コミットについての一言" を使えば便利だと思います -m は 「メッセージ」(コミットについての一言)を追加するためにあります。

Commitがそれぞれユニークなhashをもつ、これによってギットがコミットを管理します(英語ですが、How is git commit sha1 formed · GitHub で、そのハッシュがどうやって作られてるのかを調べた人もいました)

では、git log でコミット歴史を見ましょう

f:id:c-pattamada:20180705160518p:plain

黄色文字列が今回のコミットハッシュです。あとでハッシュを使うものも見ましょう! (ボーナス:githubのコミットの管理画面からいかの画像のようなコミットが見れます、そこで右側の青いものをクリックするとhashをコピーできます ^^) f:id:c-pattamada:20180705160748p:plain 

Commitしました!で..?

ローカルのgitを定期的にオンラインにpushしましょう!これで、バックアップとなり、他の人とシェアしたり共同開発ができるようになります! 先ほどのlevel 0で設定したoriginがpush先になります。 では、pushしましょう! git push -u origin masterを打てば、現在使ってるmasterのbranch(後で詳細書きます)がオンラインにも現れます。 -u が最初の時に使った方がいいですが、その後でgit pushでできます。

これでオンライン上のgit repositoryに行きますと、自分のpushの結果が見れます。

あー間違いました!

だいたい二つの状況があるかと思います。

コミットする前に、やっぱりいまの変更が失敗だと判断して、前のセーブに戻りたい

つまり、ボスに倒されて、直前のセーブの状態にresetをしたい。 まだコミットしてない状態なら、 git checkout <ファイル名> を使って、ローカルの変更を捨てられます。 注意:捨てられた変更は失われます。

一つのファイルではなく、全ファイルの場合に git checkout .が使えます。(両方ともremoteが設定されてる前提です。)

コミットをした後で後悔中です!

こういうのもよくあるかと思います。間違ったファイルをcommitしたり、アップしたら他の人が作ってる部分がなぜか壊れたりするケースもあるかと思います。ボスに負けたのに、間違って保存しちゃった気分って最悪ですが、gitの場合には問題ありません。

手段1: git reset そのままに使うと、「add」されたファイルが全てaddされてない状態になるのですが、ローカルの内容を全て捨てたい場合もあります(他の人とかぶりがありました!など)。

git reset --hard origin/<branch_name> [(branch_name_)を確認するために git branch を書いて、*がついてるのが現在のブランチ] 
注意:このやり方でローカルのものが無くなります。 十分注意してください。

手段2: もし、すでにプッシュされた、昔のものであれば、git revert <commit hash> で昔にあったコミットを戻すような新しいコミットを作成します。このやり方では何も無くなりませんが、実装がちょっとめんどいです。 例えば、今回のコミットをrevertしたい場合には git revert 0e7cfc6b8cac249f8fd2445c86660d6805045b9d を書きますと....

f:id:c-pattamada:20180705161159p:plain

あら...コミットが消えましたね。今回作成したファイルも消えました!

まとめ

今回Gitの基本コマンドについて書きました。init, status, add, push, commit, resetなどを使って見てください! 以外と説明が長くなってしまいました。そのため、全部一つに記事に打っ込むよりみつに分けることにしました!続きも楽しみにしてください!今度はLevel 2: branch, merge, fetch, pullなどについて書きますね!