Skip to content

Commit

Permalink
Merge pull request #2 from ryichk/add-todo-list-api
Browse files Browse the repository at this point in the history
Todoリストを取得するAPIを追加
  • Loading branch information
ryichk authored Dec 30, 2024
2 parents 3a2fee5 + 729ed95 commit e2c1361
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 13 deletions.
3 changes: 3 additions & 0 deletions api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ module github.com/ryichk/todolist/api
go 1.23.3

require (
github.com/MicahParks/keyfunc/v3 v3.3.5
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/jackc/pgx/v5 v5.7.2
github.com/labstack/echo/v4 v4.13.3
github.com/labstack/gommon v0.4.2
)

require (
github.com/MicahParks/jwkset v0.5.19 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
Expand Down
6 changes: 6 additions & 0 deletions api/go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
github.com/MicahParks/jwkset v0.5.19 h1:XZCsgJv05DBCvxEHYEHlSafqiuVn5ESG0VRB331Fxhw=
github.com/MicahParks/jwkset v0.5.19/go.mod h1:q8ptTGn/Z9c4MwbcfeCDssADeVQb3Pk7PnVxrvi+2QY=
github.com/MicahParks/keyfunc/v3 v3.3.5 h1:7ceAJLUAldnoueHDNzF8Bx06oVcQ5CfJnYwNt1U3YYo=
github.com/MicahParks/keyfunc/v3 v3.3.5/go.mod h1:SdCCyMJn/bYqWDvARspC6nCT8Sk74MjuAY22C7dCST8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
Expand Down
31 changes: 31 additions & 0 deletions api/internal/handler/conn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package handler

import (
"context"

"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/labstack/echo/v4"
)

type UserInfo struct {
Conn *pgxpool.Conn
UserID pgtype.UUID
}

func (h *Handler) AcquireConnection(ctx context.Context, c echo.Context) (*UserInfo, error) {
userID, err := UserIDByContext(c)
if err != nil {
return nil, err
}

conn, err := h.queries.AcquireConnection(ctx, h.pool)
if err != nil {
return nil, err
}

return &UserInfo{
Conn: conn,
UserID: userID,
}, nil
}
7 changes: 0 additions & 7 deletions api/internal/handler/handler.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package handler

import (
"net/http"

"github.com/jackc/pgx/v5/pgxpool"
"github.com/labstack/echo/v4"
"github.com/ryichk/todolist/api/internal/model"
)

Expand All @@ -16,7 +13,3 @@ type Handler struct {
func NewHandler(pool *pgxpool.Pool, queries *model.Queries) *Handler {
return &Handler{pool: pool, queries: queries}
}

func (h *Handler) Hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello!")
}
11 changes: 11 additions & 0 deletions api/internal/handler/hello.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package handler

import (
"net/http"

"github.com/labstack/echo/v4"
)

func (h *Handler) Hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello!")
}
22 changes: 22 additions & 0 deletions api/internal/handler/param.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package handler

import (
"errors"

"github.com/jackc/pgx/v5/pgtype"
"github.com/labstack/echo/v4"
)

func UserIDByContext(c echo.Context) (pgtype.UUID, error) {
userIDStr, ok := c.Get("userID").(string)
if !ok {
return pgtype.UUID{}, errors.New("user ID is not a string")
}

var userID pgtype.UUID
if err := userID.Scan(userIDStr); err != nil {
return pgtype.UUID{}, err
}

return userID, nil
}
25 changes: 21 additions & 4 deletions api/internal/handler/todo.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
package handler

// func (h *Handler) ListTodos(c echo.Context) error {
// rc := c.Request().Context()
import (
"net/http"

// todos, err := h.queries.ListTodos(rc)
// }
"github.com/labstack/echo/v4"
)

func (h *Handler) ListTodos(c echo.Context) error {
ctx := c.Request().Context()

userInfo, err := h.AcquireConnection(ctx, c)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
defer userInfo.Conn.Release()

todos, err := h.queries.ListTodos(ctx, userInfo.Conn)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}

return c.JSON(http.StatusOK, todos)
}
16 changes: 16 additions & 0 deletions api/internal/model/conn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package model

import (
"context"

"github.com/jackc/pgx/v5/pgxpool"
)

func (q *Queries) AcquireConnection(ctx context.Context, pool *pgxpool.Pool) (*pgxpool.Conn, error) {
conn, err := pool.Acquire(ctx)
if err != nil {
return nil, err
}

return conn, nil
}
83 changes: 83 additions & 0 deletions api/internal/server/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package server

import (
"errors"
"fmt"
"log"
"net/http"
"os"
"time"

"github.com/MicahParks/keyfunc/v3"
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo/v4"
)

func AuthMiddleware() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
jwtBase64 := extractJWTFromHeader(c.Request())
if jwtBase64 == "" {
return echo.NewHTTPError(http.StatusUnauthorized, "JWT is required")
}

claims, err := validateJWT(jwtBase64)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, err)
}
c.Set("userID", claims.Subject)

return next(c)
}
}
}

type CustomClaims struct {
jwt.RegisteredClaims
}

func validateJWT(jwtBase64 string) (*CustomClaims, error) {
regionID := os.Getenv("AWS_REGION")
userPoolID := os.Getenv("COGNITO_USER_POOL_ID")

jwksURL := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json", regionID, userPoolID)
jwks, err := keyfunc.NewDefault([]string{jwksURL})
if err != nil {
log.Printf("Failed to create JWK Set from resource at the given URL.\nError: %v", err)
return nil, errors.New("Failed to create JWT Set")
}

issuer := fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s", regionID, userPoolID)
token, err := jwt.ParseWithClaims(jwtBase64, &CustomClaims{}, jwks.Keyfunc, jwt.WithExpirationRequired(), jwt.WithIssuer(issuer))
if err != nil {
log.Printf("Failed to parse the JWT.\nError: %v", err)
return nil, errors.New("Failed to parse the JWT")
}
if !token.Valid {
log.Println("The token is invalid.")
return nil, errors.New("The token is invalid")
}
claims, ok := token.Claims.(*CustomClaims)
if !ok {
log.Println("The token has invalid claims.")
return nil, errors.New("The token has invalid claims")
}
if claims.ExpiresAt != nil && claims.ExpiresAt.Before(time.Now()) {
log.Println("The token has expired.")
return nil, errors.New("The token has expired")
}
if claims.NotBefore != nil && claims.NotBefore.After(time.Now()) {
log.Println("The token is not valid yet.")
return nil, errors.New("The token is not valid yet")
}

return claims, nil
}

func extractJWTFromHeader(r *http.Request) string {
authHeader := r.Header.Get("Authorization")
if len(authHeader) > 7 && authHeader[:7] == "Bearer " {
return authHeader[7:]
}
return ""
}
9 changes: 7 additions & 2 deletions api/internal/server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import (
"github.com/ryichk/todolist/api/internal/model"
)

func PrivateRoutes(e *echo.Echo, h *handler.Handler, db *pgxpool.Pool, queries *model.Queries) {
// e.GET("/todos", h.ListTodos)
func PublicRoutes(e *echo.Echo, h *handler.Handler, db *pgxpool.Pool, queries *model.Queries) {
e.GET("/", h.Hello)
}

func PrivateRoutes(e *echo.Echo, h *handler.Handler, db *pgxpool.Pool, queries *model.Queries) {
e.Use(AuthMiddleware())

e.GET("/todos", h.ListTodos)
}
1 change: 1 addition & 0 deletions api/internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func NewServer(db *pgxpool.Pool) (*echo.Echo, error) {
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
}))

PublicRoutes(e, h, db, queries)
PrivateRoutes(e, h, db, queries)

return e, nil
Expand Down

0 comments on commit e2c1361

Please sign in to comment.