[Advent Calendar 2021][Day21]カレンダーの更新削除を実装する(1)

undefined

はじめに

Advent Calendar 2021のDay21。 今回は、カレンダーの更新削除を実装していきます。

Hasura Action

HasuraConsoleでActionを定義します。 HasuraConsoleのEditorを使ってGraphQLのクエリを定義します カレンダー、参加者の作成・更新のアクションを追加していきます。 削除は、HasuraのMutationをそのまま使おうと思いますので、Nuxtの実装だけになります。次回以降対応します。 エクスポートしたデータをみるとイメージができるかと思います。

hasura metadata export
  • metadata/actions.graphql
type Mutation {  actionCreateCalendar(    in: ActionCreateCalendarInput!  ): ActionCreateCalendarOutput}type Mutation {  actionCreateParticipant(    in: ActionCreateParticipantInput!  ): ActionCreateParticipantOutput}type Mutation {  actionUpdateCalendar(    in: ActionUpdateCalendarInput!  ): ActionUpdateCalendarOutput}type Mutation {  actionUpdateParticipant(    in: ActionUpdateParticipantInput!  ): ActionUpdateParticipantOutput}input ActionCreateCalendarInput {  name: String}input ActionUpdateCalendarInput {  id: Int!  name: String}input ActionCreateParticipantInput {  calendar_id: Int!  day: Int!  article_title: String  article_url: String}input ActionUpdateParticipantInput {  calendar_id: Int!  day: Int!  article_title: String  article_url: String}type ActionCreateCalendarOutput {  id: Int  name: String  uid: String  created_at: Time  updated_at: Time}type ActionUpdateCalendarOutput {  id: Int  name: String  uid: String  created_at: Time  updated_at: Time}type ActionCreateParticipantOutput {  calendar_id: Int!  day: Int!  uid: String  article_title: String  article_url: String  created_at: Time  updated_at: Time}type ActionUpdateParticipantOutput {  calendar_id: Int!  day: Int!  uid: String  article_title: String  article_url: String  created_at: Time  updated_at: Time}scalar Mapscalar Timescalar Void

permissionにUserを指定しています。Actionを実装したら、Hasuraが提供するMutationのInsertやUpdateは許可設定を拒否するようにするとよいです。

  • metadata/actions.yaml
actions:- name: actionCreateCalendar  definition:    kind: synchronous    handler: '{{ACTION_BASE_URL}}/events/hasura/action'  permissions:  - role: user- name: actionCreateParticipant  definition:    kind: synchronous    handler: '{{ACTION_BASE_URL}}/events/hasura/action'  permissions:  - role: user- name: actionUpdateCalendar  definition:    kind: synchronous    handler: '{{ACTION_BASE_URL}}/events/hasura/action'  permissions:  - role: user- name: actionUpdateParticipant  definition:    kind: synchronous    handler: '{{ACTION_BASE_URL}}/events/hasura/action'  permissions:  - role: usercustom_types:  enums: []  input_objects:  - name: ActionCreateCalendarInput  - name: ActionUpdateCalendarInput  - name: ActionCreateParticipantInput  - name: ActionUpdateParticipantInput  objects:  - name: ActionCreateCalendarOutput  - name: ActionUpdateCalendarOutput  - name: ActionCreateParticipantOutput  - name: ActionUpdateParticipantOutput  scalars:  - name: Map  - name: Time  - name: Void

Action Handlerを実装する(途中)

HasuraのAction HandlerをGoで実装します。 HasuraのCLIをつかってCodegenできればよいのですが、バグなのかきちんと動作してくれないので、自分で実装します。Hasura Consoleに表示されるCodegenの内容を参考にします。エンドポイントは1箇所で実装しています。

mkdir goappcd goapp go mod init
  • goapp/main.go
package mainimport (    "fmt"    "log"    "net/http"    "os")func main() {    port := os.Getenv("PORT")    mux := http.NewServeMux()    mux.HandleFunc("/events/hasura/action", handler)    err := http.ListenAndServe(fmt.Sprintf(":%s", port), mux)    log.Fatal(err)}

ハンドラー部分は、Day22で追加実装していきます。Hasura Actionの説明にあるようにデータバリデーションやビジネスロジックなどを実装する用途で使用します。

参考

https://hasura.io/docs/latest/graphql/core/actions/index.html#what-are-actions

  • goapp/handler.go
package mainimport (    "encoding/json"    "io/ioutil"    "log"    "net/http"    "github.com/mitchellh/mapstructure")type Action struct {    Name string `json:"name"`}type ActionPayload struct {    Action           Action                 `json:"action"`    SessionVariables map[string]interface{} `json:"session_variables"`    Input            interface{}            `json:"input"`}type GraphQLError struct {    Message string `json:"message"`}func handler(w http.ResponseWriter, r *http.Request) {    log.Println("Received request")    // set the response header as JSON    w.Header().Set("Content-Type", "application/json")    // read request body    reqBody, err := ioutil.ReadAll(r.Body)    if err != nil {        log.Println(err)        http.Error(w, "invalid payload", http.StatusBadRequest)        return    }    // parse the body as action payload    var actionPayload ActionPayload    err = json.Unmarshal(reqBody, &actionPayload)    if err != nil {        log.Println(err)        http.Error(w, "invalid payload", http.StatusBadRequest)        return    }    // Send the request params to the Action's generated handler function    var result interface{}    switch actionPayload.Action.Name {    case "actionCreateCalendar":        args := actionCreateCalendarArgs{}        if err := mapstructure.Decode(actionPayload.Input, &args); err != nil {            log.Println(err)            http.Error(w, "invalid payload", http.StatusBadRequest)            return        }        result, err = actionCreateCalendar(args)    case "actionUpdateCalendar":        // TODO: implement    case "actionCreateParticipant":        // TODO: implement    case "actionUpdateParticipant":        // TODO: implement    }    // throw if an error happens    if err != nil {        errorObject := GraphQLError{            Message: err.Error(),        }        errorBody, _ := json.Marshal(errorObject)        w.WriteHeader(http.StatusBadRequest)        w.Write(errorBody)        return    }    // Write the response as JSON    data, _ := json.Marshal(result)    w.Write(data)}// Auto-generated function that takes the Action parameters and must return it's response typefunc actionCreateCalendar(args actionCreateCalendarArgs) (response ActionCreateCalendarOutput, err error) {    // TODO: implement    response = ActionCreateCalendarOutput{}    return response, nil}func actionCreateParticipant(args actionCreateParticipantArgs) (response ActionCreateParticipantOutput, err error) {    // TODO: implement    response = ActionCreateParticipantOutput{}    return response, nil}func actionUpdateCalendar(args actionUpdateCalendarArgs) (response ActionUpdateCalendarOutput, err error) {    // TODO: implement    response = ActionUpdateCalendarOutput{}    return response, nil}func actionUpdateParticipant(args actionUpdateParticipantArgs) (response ActionUpdateParticipantOutput, err error) {    // TODO: implement    response = ActionUpdateParticipantOutput{}    return response, nil}

型情報もHasuraConsoleに表示されますが、JSONタグがなかったりするので、この辺は、Codegenほしいなと思いますね。Actionの数が増えてくると辛くなります。

  • goapp/types.go
package maintype Map stringtype Time stringtype Void stringtype ActionCreateCalendarInput struct {    Name *string `json:"name,omitempty"`}type ActionUpdateCalendarInput struct {    Id   int     `json:"id"`    Name *string `json:"name,omitempty"`}type ActionCreateParticipantInput struct {    Calendar_id   int     `json:"calendar_id"`    Day           int     `json:"day"`    Article_title *string `json:"article_title,omitempty"`    Article_url   *string `json:"article_url,omitempty"`}type ActionUpdateParticipantInput struct {    Calendar_id   int     `json:"calendar_id"`    Day           int     `json:"day"`    Article_title *string `json:"article_title,omitempty"`    Article_url   *string `json:"article_url,omitempty"`}type ActionCreateCalendarOutput struct {    Id         *int    `json:"id,omitempty"`    Name       *string `json:"name,omitempty"`    Uid        *string `json:"uid,omitempty"`    Created_at *Time   `json:"created_at,omitempty"`    Updated_at *Time   `json:"updated_at,omitempty"`}type ActionUpdateCalendarOutput struct {    Id         *int    `json:"id,omitempty"`    Name       *string `json:"name,omitempty"`    Uid        *string `json:"uid,omitempty"`    Created_at *Time   `json:"created_at,omitempty"`    Updated_at *Time   `json:"updated_at,omitempty"`}type ActionCreateParticipantOutput struct {    Calendar_id   int     `json:"calendar_id,omitempty"`    Day           int     `json:"day,omitempty"`    Uid           *string `json:"uid,omitempty"`    Article_title *string `json:"article_title,omitempty"`    Article_url   *string `json:"article_url,omitempty"`    Created_at    *Time   `json:"created_at,omitempty"`    Updated_at    *Time   `json:"updated_at,omitempty"`}type ActionUpdateParticipantOutput struct {    Calendar_id   int     `json:"calendar_id,omitempty"`    Day           int     `json:"day,omitempty"`    Uid           *string `json:"uid,omitempty"`    Article_title *string `json:"article_title,omitempty"`    Article_url   *string `json:"article_url,omitempty"`    Created_at    *Time   `json:"created_at,omitempty"`    Updated_at    *Time   `json:"updated_at,omitempty"`}type Mutation struct {    ActionCreateCalendar    *ActionCreateCalendarOutput    ActionCreateParticipant *ActionCreateParticipantOutput    ActionUpdateCalendar    *ActionUpdateCalendarOutput    ActionUpdateParticipant *ActionUpdateParticipantOutput}type actionCreateCalendarArgs struct {    In ActionCreateCalendarInput `json:"in"`}type actionCreateParticipantArgs struct {    In ActionCreateParticipantInput `json:"in"`}type actionUpdateCalendarArgs struct {    In ActionUpdateCalendarInput `json:"in"`}type actionUpdateParticipantArgs struct {    In ActionUpdateParticipantInput `json:"in"`}

これもCloudRunで動かそうと思いますので、Dockerfileを準備しておきます。

FROM golang:1.17-bullseye as build

WORKDIR /go/src/app
ADD . /go/src/app

RUN go get -d -v ./...

RUN go build -o /go/bin/app

FROM gcr.io/distroless/base-debian11
COPY --from=build /go/bin/app /
CMD ["/app"]

まとめ

今回は、カレンダーの更新削除を実装しました。 次回も引き続き、カレンダーの更新削除を実装していきます。 主にハンドラー部分の実装をします。


クラウドアンドビルド株式会社

Cloud and Build, Inc.

クラウドアンドビルド株式会社は、Google Cloud パートナー企業です。

GCP 導入から開発・コンサルティングまでワンストップでお任せください。

お問い合わせ

デジタル変革を開始しますか?

お気軽にお問い合わせください。

contact_at_cloudandbuild.jp
所在地

〒104-0061

東京都中央区銀座2-14-8

ML20191021

Designed by Freepik
© 2020 Cloud and Build, Inc.