Skip to content

Commit

Permalink
Merge pull request #11 from naohito-T/feature/openapi
Browse files Browse the repository at this point in the history
Feature/openapi
  • Loading branch information
naohito-T authored May 5, 2024
2 parents 6e2c48d + 5ff53b8 commit b342675
Show file tree
Hide file tree
Showing 20 changed files with 410 additions and 192 deletions.
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# tinyurl

<details>
<summary style="font-size: 20px">🔱 CI Actions Status</summary>
[![Test Backend](https://github.com/naohito-T/tinyurl/actions/workflows/test_backend.yml/badge.svg?branch=main)](https://github.com/naohito-T/tinyurl/actions/workflows/test_backend.yml)
</details>
[![Test Backend](https://github.com/naohito-T/tinyurl/actions/workflows/test_backend.yml/badge.svg?branch=main)](https://github.com/naohito-T/tinyurl/actions/workflows/test_backend.yml)

## Overview

Expand Down
5 changes: 4 additions & 1 deletion backend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ lint-f:
build:
GOARCH=arm64 GOOS=linux go build -o bin/main cmd/api/main.go

build-openapi:
go run ./cmd/api/main.go openapi >./api/openapi.yaml

# --------------------------
# Utils
# --------------------------
Expand All @@ -48,4 +51,4 @@ dynamo.admin:
export PORT="4005" && \
npx dynamodb-admin

.PHONY: test sec errcheck staticcheck format lint-all lint build test dynamo.admin
.PHONY: dev test sec errcheck staticcheck format lint-all lint build build-openapi dynamo.admin
160 changes: 160 additions & 0 deletions backend/api/openapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
components:
schemas:
ErrorDetail:
additionalProperties: false
properties:
location:
description: Where the error occurred, e.g. 'body.items[3].tags' or 'path.thing-id'
type: string
message:
description: Error message text
type: string
value:
description: The value at the given location
type: object
ErrorModel:
additionalProperties: false
properties:
$schema:
description: A URL to the JSON Schema for this object.
examples:
- http://localhost:6500/api/v1/schemas/ErrorModel.json
format: uri
readOnly: true
type: string
detail:
description: A human-readable explanation specific to this occurrence of the problem.
examples:
- Property foo is required but is missing.
type: string
errors:
description: Optional list of individual error details
items:
$ref: "#/components/schemas/ErrorDetail"
type: array
instance:
description: A URI reference that identifies the specific occurrence of the problem.
examples:
- https://example.com/error-log/abc123
format: uri
type: string
status:
description: HTTP status code
examples:
- 400
format: int64
type: integer
title:
description: A short, human-readable summary of the problem type. This value should not change between occurrences of the error.
examples:
- Bad Request
type: string
type:
default: about:blank
description: A URI reference to human-readable documentation for the error.
examples:
- https://example.com/errors/example
format: uri
type: string
type: object
GreetingOutput3Body:
additionalProperties: false
properties:
$schema:
description: A URL to the JSON Schema for this object.
examples:
- http://localhost:6500/api/v1/schemas/GreetingOutput3Body.json
format: uri
readOnly: true
type: string
message:
description: Greeting message
examples:
- Hello, world!
type: string
required:
- message
type: object
HealthCheckParams2Body:
additionalProperties: false
properties:
$schema:
description: A URL to the JSON Schema for this object.
examples:
- http://localhost:6500/api/v1/schemas/HealthCheckParams2Body.json
format: uri
readOnly: true
type: string
message:
description: Greeting message
examples:
- Hello, world!
type: string
required:
- message
type: object
securitySchemes:
bearer:
bearerFormat: JWT
scheme: bearer
type: http
info:
title: TinyURL
version: 1.0.0
openapi: 3.1.0
paths:
/greeting/{name}:
get:
description: Get a greeting for a person by name.
operationId: get-greeting
parameters:
- description: Name to greet
example: world
in: path
name: name
required: true
schema:
description: Name to greet
examples:
- world
maxLength: 30
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/GreetingOutput3Body"
description: OK
default:
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ErrorModel"
description: Error
summary: Get a greeting
tags:
- Greetings
/health:
get:
description: Check the health of the service.
operationId: health
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/HealthCheckParams2Body"
description: OK
default:
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ErrorModel"
description: Error
summary: Health Check
tags:
- Greetings
servers:
- url: http://localhost:6500/api/v1

36 changes: 20 additions & 16 deletions backend/cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,25 @@ type Options struct {
Port int `doc:"Port to listen on." short:"p" default:"8888"`
}

// /api/v1/openapi.yaml
// initHuma: humaのconfigを初期化
func initHuma() huma.Config {
config := huma.DefaultConfig(configs.OpenAPITitle, configs.OpenAPIVersion)
config.Servers = []*huma.Server{
{URL: configs.OpenAPIDocServerPath},
}

config.Components.SecuritySchemes = map[string]*huma.SecurityScheme{
"bearer": {
Type: "http",
Scheme: "bearer",
BearerFormat: "JWT",
},
}
config.DocsPath = "/docs"
return config
}

// publicにわける
// user(ログイン必須)
// private(管理者)
Expand All @@ -38,25 +57,10 @@ func main() {
e := echo.New()
// configを初期化
configs.NewAppEnvironment()
config := huma.DefaultConfig(configs.OpenAPITitle, configs.OpenAPIVersion)
// Openapiのserver設定
config.Servers = []*huma.Server{
{URL: "http://localhost:6500/api/v1"},
}

config.Components.SecuritySchemes = map[string]*huma.SecurityScheme{
"bearer": {
Type: "http",
Scheme: "bearer",
BearerFormat: "JWT",
},
}
config.DocsPath = "/docs"
// ミドルウェアを適用(すべてのリクエストに対して)
middleware.CustomMiddleware(e)
// /api/v1/openapi.yaml
// これgroup化したやつをnewUserRouterに渡す必要かも
api = humaecho.NewWithGroup(e, e.Group("/api/v1"), config)
api = humaecho.NewWithGroup(e, e.Group("/api/v1"), initHuma())
router.NewPublicRouter(api)

// 未定義のルート用のキャッチオールハンドラ
Expand Down
35 changes: 33 additions & 2 deletions backend/configs/constructor.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
package configs

const (
// ApplicationPort is the port the application listens on.
ApplicationPort = ":6500"
OpenAPITitle = "TinyURL"
OpenAPIVersion = "1.0.0"
// OpenAPITitle is the title of the OpenAPI spec.
OpenAPITitle = "TinyURL API"
// OpenAPIVersion is the version of the OpenAPI spec.
OpenAPIVersion = "1.0.0"
// OpenAPIServerPath is the base URL for the OpenAPI spec.
OpenAPIDocServerPath = "http://localhost:6500/api/v1"
)

// OperationID: このAPI操作の一意の識別子。これは、API内で操作を参照する際に使用されます。
// Method: HTTPメソッドを指定します。この例では http.MethodGet が使われており、これはHTTPのGETリクエストを示します。
// Path: エンドポイントのURLパスを指定します。ここでは "/greeting/{name}" となっており、{name} はパスパラメータを表しています。
// Summary: 短い説明文です。APIのドキュメントに表示され、APIの目的を簡潔に説明します。
// Description: APIエンドポイントの詳細な説明です。ここでは操作の詳細や動作についての追加情報を提供します。
// Tags: このAPI操作に関連付けられたタグのリストです。これにより、APIドキュメント内で類似の操作をグループ化することができます。

// huma.Register(app, huma.Operation{
// OperationID: "health",
// Method: http.MethodGet,
// Path: Router.Health,
// Summary: "Health Check",
// Description: "Check the health of the service.",
// Tags: []string{"Public"},
// }, func(_ context.Context, _ *HealthCheckParams) (*HealthCheckQuery, error) {
// resp := &HealthCheckQuery{
// Body: struct{
// Message string `json:"message,omitempty" example:"Hello, world!" doc:"Greeting message"`
// }{
// Message: "ok",
// },
// }
// fmt.Printf("Health Check: %v\n", resp.Body.Message)
// return resp, nil
// })
9 changes: 6 additions & 3 deletions backend/configs/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ package configs
type path = string

const (
Health path = "/health"
GetShortURL path = "/api/v1/urls/:shortUrl"
CreateShortURL path = "/api/v1/urls"
// /api/v1/health
Health path = "/health"
// /api/v1/urls
GetShortURL path = "/urls/:id"
// /api/v1/urls
CreateShortURL path = "/urls"
)
2 changes: 0 additions & 2 deletions backend/docker/localstack/init-aws.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ echo "init dynamoDB"
awslocal dynamodb create-table --table-name offline-tinyurls \
--attribute-definitions \
AttributeName=id,AttributeType=S \
AttributeName=originalURL,AttributeType=S \
AttributeName=createdAt,AttributeType=S \
--key-schema \
AttributeName=id,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
Expand Down
20 changes: 19 additions & 1 deletion backend/domain/shorturl.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
package domain

import (
"crypto/sha1"

Check failure on line 4 in backend/domain/shorturl.go

View workflow job for this annotation

GitHub Actions / lint-backend

G505: Blocklisted import crypto/sha1: weak cryptographic primitive (gosec)
"encoding/base64"
"strings"
"time"
)

type ShortURL struct {
ID string `json:"id"`
OriginalURL string `json:"original"`
OriginalURL string `json:"original_url"`
CreatedAt string `json:"created_at"`
}

func GenerateShortURL(originalURL string) *ShortURL {
hasher := sha1.New()

Check failure on line 17 in backend/domain/shorturl.go

View workflow job for this annotation

GitHub Actions / lint-backend

G401: Use of weak cryptographic primitive (gosec)
hasher.Write([]byte(originalURL))
sha := base64.URLEncoding.EncodeToString(hasher.Sum(nil))
return &ShortURL{
ID: strings.TrimRight(sha, "=")[:7],
OriginalURL: originalURL,
CreatedAt: time.Now().Format(time.RFC3339),
}
}
8 changes: 0 additions & 8 deletions backend/domain/url.go

This file was deleted.

4 changes: 2 additions & 2 deletions backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.27.11
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.13
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1
github.com/danielgtaylor/huma/v2 v2.14.0
github.com/aws/smithy-go v1.20.2
github.com/danielgtaylor/huma/v2 v2.15.0
github.com/go-playground/validator/v10 v10.19.0
github.com/kelseyhightower/envconfig v1.4.0
github.com/labstack/echo/v4 v4.11.4
Expand All @@ -28,7 +29,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/sso v1.20.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect
github.com/aws/smithy-go v1.20.2 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
Expand Down
4 changes: 2 additions & 2 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.28.6/go.mod h1:FZf1/nKNEkHdGGJP/cI2M
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/danielgtaylor/huma/v2 v2.14.0 h1:lRuhQQPZhePvJ4B/m4kfIUYfD8ZPy5BKq/oktLFmB50=
github.com/danielgtaylor/huma/v2 v2.14.0/go.mod h1:OdHC/JliXtOrnvHLQTU5qV7WvYRQXwWY1tkl5rLXmuE=
github.com/danielgtaylor/huma/v2 v2.15.0 h1:26c3hxNT+0xNc8qDLPXNko48qyi31RDFQdhi36gorRI=
github.com/danielgtaylor/huma/v2 v2.15.0/go.mod h1:OdHC/JliXtOrnvHLQTU5qV7WvYRQXwWY1tkl5rLXmuE=
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=
Expand Down
14 changes: 13 additions & 1 deletion backend/internal/infrastructures/dynamo/dynamo.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"log/slog"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
)
Expand All @@ -13,7 +14,18 @@ type Connection struct {
}

func NewDynamoConnection() *Connection {
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("ap-northeast-1"))
// https://zenn.dev/y16ra/articles/40ff14e8d2a4db
customResolver := aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {

Check failure on line 18 in backend/internal/infrastructures/dynamo/dynamo.go

View workflow job for this annotation

GitHub Actions / lint-backend

SA1019: aws.EndpointResolverFunc is deprecated: See EndpointResolverWithOptionsFunc (staticcheck)
return aws.Endpoint{
PartitionID: "aws",
URL: "http://aws:4566", // LocalStackのDynamoDBエンドポイント
SigningRegion: "ap-northeast-1",
}, nil
})
cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithRegion("ap-northeast-1"),
config.WithEndpointResolver(customResolver),

Check failure on line 27 in backend/internal/infrastructures/dynamo/dynamo.go

View workflow job for this annotation

GitHub Actions / lint-backend

SA1019: config.WithEndpointResolver is deprecated: See WithEndpointResolverWithOptions (staticcheck)
)
if err != nil {
slog.Error("unable to load SDK config, %v", err)
}
Expand Down
Loading

0 comments on commit b342675

Please sign in to comment.