diff --git a/.github/workflows/init.go b/.github/workflows/init.go new file mode 100644 index 0000000..054bea4 --- /dev/null +++ b/.github/workflows/init.go @@ -0,0 +1,46 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "os" + + _ "github.com/go-sql-driver/mysql" +) + +func main() { + conn, err := sql.Open("mysql", fmt.Sprintf( + "%s:%s@tcp(%s:%s)/?charset=utf8mb4&parseTime=true", + getEnvOrDefault("MYSQL_USERNAME", "root"), + getEnvOrDefault("MYSQL_PASSWORD", "password"), + getEnvOrDefault("MYSQL_HOST", "127.0.0.1"), + getEnvOrDefault("MYSQL_PORT", "3306"), + )) + if err != nil { + panic(err) + } + defer conn.Close() + + dbs := []string{ + "booq_test", + } + + for _, name := range dbs { + if _, err = conn.Exec("DROP DATABASE IF EXISTS " + name); err != nil { + panic(err) + } + if _, err = conn.Exec("CREATE DATABASE `" + name + "` CHARACTER SET = utf8mb4"); err != nil { + panic(err) + } + log.Println("Database `" + name + "` was created") + } +} + +func getEnvOrDefault(env string, def string) string { + s := os.Getenv(env) + if len(s) == 0 { + return def + } + return s +} diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..aad81f2 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,81 @@ +name: CI + +on: + push: + branches: + - "main" + pull_request: + +jobs: + build: + name: Server build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + - name: mod + run: go mod download + - name: build + run: go build + - uses: actions/upload-artifact@v4 + with: + name: booQ + path: booQ + lint: + name: Server lint + runs-on: ubuntu-latest + needs: [build] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + - name: Install reviewdog + run: curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh | sh -s -- -b $(go env GOPATH)/bin + - name: Install golangci-lint + run: curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin + - name: golangci-lint + run: golangci-lint run --out-format=line-number | reviewdog -f=golangci-lint -name=golangci-lint -reporter=github-check + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + test: + name: Server test + runs-on: ubuntu-latest + needs: [build] + env: + MYSQL_USER: root + MYSQL_PASSWORD: password + MYSQL_DATABASE: booq_test + services: + mysql: + image: mariadb:10.11.7 + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + env: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: booq_test + ports: + - 3306:3306 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + - name: Setup DB + run: go run .github/workflows/init.go + - name: Run model tests + run: go test . ./model -v -covermode=atomic -vet=off + - name: Run router tests + run: go test . ./router -v -covermode=atomic -vet=off + spectral: + name: OpenApi Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Spectral checks + uses: stoplightio/spectral-action@v0.8.11 + with: + file_glob: docs/swagger.yml + repo_token: ${{ secrets.GITHUB_TOKEN }} + diff --git a/Dockerfile b/Dockerfile index 2dcfe96..5a0bfea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20.0-alpine AS build +FROM golang:1.22.0-alpine AS build ENV CGO_ENABLED=0 ENV DOCKERIZE_VERSION v0.6.1 RUN apk add --update --no-cache git && \ diff --git a/compose-dev.yml b/compose-dev.yml index 7e75570..b30e30f 100644 --- a/compose-dev.yml +++ b/compose-dev.yml @@ -4,14 +4,14 @@ services: image: mariadb:10.6.4 environment: MYSQL_ROOT_PASSWORD: password - MYSQL_DATABASE: booq + MYSQL_DATABASE: booq-v3 MYSQL_USERNAME: root MYSQL_PASSWORD: password command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci expose: - - '3306' + - "3306" ports: - - '3306:3306' + - "3306:3306" healthcheck: test: mysqladmin ping -h 127.0.0.1 -u$$MYSQL_USERNAME -p$$MYSQL_ROOT_PASSWORD interval: 6s @@ -26,13 +26,13 @@ services: MYSQL_HOST: db MYSQL_USER: root MYSQL_PASSWORD: password - MYSQL_DATABASE: booq + MYSQL_DATABASE: booq-v3 DEBUG_USER_NAME: ryoha volumes: - - './:/app' + - "./:/app" tty: true ports: - - '8080:3001' + - "8080:3001" depends_on: db: condition: service_healthy @@ -45,3 +45,4 @@ services: API_URL: sample.yaml ports: - "4000:8080" + diff --git a/docker-compose.yml b/docker-compose.yml index 8000d09..46d1c59 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,18 @@ version: "3" services: db: - image: mariadb:10.6.4 + image: mariadb:10.11.7 restart: always environment: MYSQL_ROOT_PASSWORD: password - MYSQL_DATABASE: booq + MYSQL_DATABASE: booq-v3 MYSQL_USERNAME: root MYSQL_PASSWORD: password command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci expose: - - '3306' + - "3306" ports: - - '3306:3306' + - "3306:3306" healthcheck: test: mysqladmin ping -h 127.0.0.1 -u$$MYSQL_USERNAME -p$$MYSQL_ROOT_PASSWORD interval: 6s @@ -27,21 +27,28 @@ services: MYSQL_HOST: db MYSQL_USER: root MYSQL_PASSWORD: password - MYSQL_DATABASE: booq + MYSQL_DATABASE: booq-v3_test volumes: - - './:/app' + - "./:/app" tty: true ports: - - '8080:3001' + - "8080:3001" depends_on: db: condition: service_healthy - swagger: - image: swaggerapi/swagger-ui - volumes: - - ./docs/swagger.yml:/usr/share/nginx/html/sample.yaml - environment: - API_URL: sample.yaml + # swagger: + # image: swaggerapi/swagger-ui + # volumes: + # - ./docs/swagger.yml:/usr/share/nginx/html/sample.yaml + # environment: + # API_URL: sample.yaml + # ports: + # - "4000:8080" + + adminer: + image: adminer + restart: always ports: - - "4000:8080" + - 8000:8080 + diff --git a/docs/openapi.yml b/docs/openapi.yml index 99527ba..9a3950d 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -116,7 +116,9 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/itemPosted" + type: "array" + items: + $ref: "#/components/schemas/itemPosted" "400": description: "リクエストボディが不正です。" "403": @@ -520,37 +522,13 @@ components: type: integer schemas: isBook: - type: integer + type: boolean title: isBook - description: |- - アイテム種別 - 0: 本でない - 1: 本 - enum: - - 0 - - 1 - x-enum-varnames: - - notBook - - book - x-enum-descriptions: - - 本ではない物品 - - 本 + description: アイテム種別 本でない/本 isEquipment: - type: integer + type: boolean title: isEquipment - description: |- - アイテム種別 - 0: 個人所有 - 1: 備品 - enum: - - 0 - - 1 - x-enum-varnames: - - individual - - equipment - x-enum-descriptions: - - 個人所有 - - 備品 + description: アイテム種別 個人所有/備品 itemPosted: type: "object" properties: @@ -963,6 +941,9 @@ components: memo: type: "string" example: "おもしろいのでぜひ読んでください" + transaction: + type: "array" + $ref: "#/components/schemas/transaction" required: - id - itemId diff --git a/go.mod b/go.mod index 8090d31..f1d9721 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.22 require ( github.com/go-ozzo/ozzo-validation/v4 v4.3.0 + github.com/go-testfixtures/testfixtures/v3 v3.10.0 github.com/labstack/echo/v4 v4.11.4 + github.com/labstack/gommon v0.4.2 github.com/ncw/swift v1.0.53 github.com/stretchr/testify v1.8.4 gorm.io/driver/mysql v1.5.4 @@ -12,21 +14,34 @@ require ( ) require ( + github.com/ClickHouse/ch-go v0.58.2 // indirect + github.com/ClickHouse/clickhouse-go/v2 v2.18.0 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-faster/city v1.0.1 // indirect + github.com/go-faster/errors v0.6.1 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/google/uuid v1.6.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/labstack/gommon v0.4.2 // indirect + github.com/klauspost/compress v1.16.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/paulmach/orb v0.11.1 // indirect + github.com/pierrec/lz4/v4 v4.1.18 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/segmentio/asm v1.2.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sys v0.15.0 // indirect + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index db2d463..886d45d 100644 --- a/go.sum +++ b/go.sum @@ -1,56 +1,182 @@ +github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0= +github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw= +github.com/ClickHouse/clickhouse-go/v2 v2.18.0 h1:O1LicIeg2JS2V29fKRH4+yT3f6jvvcJBm506dpVQ4mQ= +github.com/ClickHouse/clickhouse-go/v2 v2.18.0/go.mod h1:ztQvX6wm7kAbhJslS87EXEhOVNY/TObXwyURnGju5FQ= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 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/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= +github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= +github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= +github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= +github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= +github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY= github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-testfixtures/testfixtures/v3 v3.10.0 h1:BrBwN7AuC+74g5qtk9D59TLGOaEa8Bw1WmIsf+SyzWc= +github.com/go-testfixtures/testfixtures/v3 v3.10.0/go.mod h1:z8RoleoNtibi6Ar8ziCW7e6PQ+jWiqbUWvuv8AMe4lo= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +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/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= +github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/ncw/swift v1.0.53 h1:luHjjTNtekIEvHg5KdAFIBaH7bWfNkefwFnpDffSIks= github.com/ncw/swift v1.0.53/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= +github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= +github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= +github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.5.4 h1:igQmHfKcbaTVyAIHNhhB888vvxh8EdQ2uSUT0LPcBso= diff --git a/main.go b/main.go index ffd1c2f..e94fed8 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" + "github.com/labstack/gommon/log" "github.com/traPtitech/booQ-v3/model" "github.com/traPtitech/booQ-v3/router" @@ -16,18 +17,29 @@ func main() { if err != nil { panic(err) } - // db.close() の必要はなさそう。参考: https://github.com/go-gorm/gorm/issues/3145 - - if os.Getenv("BOOQ_ENV") == "development" { - model.SetDBLoggerInfo() - } err = model.Migrate() if err != nil { panic(err) } - // Storage + setStorage() + + e := echo.New() + router.SetValidator(e) + + if os.Getenv("BOOQ_ENV") == "development" { + e.Logger.SetLevel(log.INFO) + } + + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + router.SetupRouting(e, router.CreateUserProvider(os.Getenv("DEBUG_USER_NAME"))) + e.Logger.Fatal(e.Start(":3001")) +} + +func setStorage() { if os.Getenv("OS_CONTAINER") != "" { // Swiftオブジェクトストレージ err := storage.SetSwiftStorage( @@ -52,20 +64,4 @@ func main() { panic(err) } } - - // Echo instance - e := echo.New() - - // Validator - router.SetValidator(e) - - // Middleware - e.Use(middleware.Logger()) - e.Use(middleware.Recover()) - - // Routing - router.SetupRouting(e, router.CreateUserProvider(os.Getenv("DEBUG_USER_NAME"))) - - // Start server - e.Logger.Fatal(e.Start(":3001")) } diff --git a/model/comments.go b/model/comments.go index 2564919..ad6c289 100644 --- a/model/comments.go +++ b/model/comments.go @@ -8,5 +8,5 @@ type Comment struct { } func (Comment) TableName() string { - return "comment" + return "comments" } diff --git a/model/comments_test.go b/model/comments_test.go deleted file mode 100644 index 8b53790..0000000 --- a/model/comments_test.go +++ /dev/null @@ -1 +0,0 @@ -package model diff --git a/model/db.go b/model/db.go index 5d2631e..14cad76 100644 --- a/model/db.go +++ b/model/db.go @@ -5,12 +5,16 @@ import ( "os" "time" + "github.com/go-testfixtures/testfixtures/v3" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" ) -var db *gorm.DB +var ( + db *gorm.DB + fixtures *testfixtures.Loader +) var allTables = []interface{}{ Item{}, @@ -45,7 +49,7 @@ func EstablishConnection() error { pass := os.Getenv("MYSQL_PASSWORD") if pass == "" { - pass = "" + pass = "password" } host := os.Getenv("MYSQL_HOST") @@ -65,8 +69,13 @@ func EstablishConnection() error { dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", user, pass, host, port, dbname) + "?parseTime=true&loc=Asia%2FTokyo&charset=utf8mb4" _db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + + if err != nil { + return err + } + db = _db - return err + return nil } func SetDBLoggerInfo() { @@ -81,3 +90,39 @@ func Migrate() error { return nil } + +// テスト用DBの稼働。model, routerのテストで用いる +func SetUpTestDB() { + err := EstablishConnection() + if err != nil { + panic(err) + } + + err = Migrate() + if err != nil { + panic(err) + } + + sqlDB, err := db.DB() + if err != nil { + panic(err) + } + + dirFixtures := "../testdata/fixtures" + fixtures, err = testfixtures.New( + testfixtures.Database(sqlDB), // You database connection + testfixtures.Dialect("mysql"), // Available: "postgresql", "timescaledb", "mysql", "mariadb", "sqlite" and "sqlserver" + testfixtures.Directory(dirFixtures), // The directory containing the YAML files + ) + + if err != nil { + wd, _ := os.Getwd() + panic(fmt.Errorf("%v %v", err, wd)) + } +} + +func PrepareTestDatabase() { + if err := fixtures.Load(); err != nil { + panic(err) + } +} diff --git a/model/db_test.go b/model/db_test.go index 8b53790..713e651 100644 --- a/model/db_test.go +++ b/model/db_test.go @@ -1 +1,12 @@ package model + +import ( + "os" + "testing" +) + +func TestMain(m *testing.M) { + SetUpTestDB() + exitCode := m.Run() + os.Exit(exitCode) +} diff --git a/model/files_test.go b/model/files_test.go deleted file mode 100644 index 8b53790..0000000 --- a/model/files_test.go +++ /dev/null @@ -1 +0,0 @@ -package model diff --git a/model/items.go b/model/items.go index 69228c8..44d607d 100644 --- a/model/items.go +++ b/model/items.go @@ -1,17 +1,23 @@ package model +import ( + "errors" + + "gorm.io/gorm" +) + type Item struct { GormModel Name string `gorm:"type:text;not null" json:"name"` Description string `gorm:"type:text;" json:"description"` ImgURL string `gorm:"type:text;" json:"imgUrl"` - Book Book `gorm:"foreignKey:item_id;references:id"` - Equipment Equipment `gorm:"foreignKey:item_id;references:id"` - Comment []Comment `gorm:"foreignKey:item_id;references:id"` - Tag []Tag `gorm:"foreignKey:item_id;references:id"` - Ownership []Ownership `gorm:"foreignKey:item_id;references:id"` - Like []Like `gorm:"foreignKey:item_id;references:id"` - TransactionEquipment []TransactionEquipment `gorm:"foreignKey:item_id;references:id"` + Book *Book `gorm:"foreignKey:item_id;references:id" json:"book,omitempty"` + Equipment *Equipment `gorm:"foreignKey:item_id;references:id" json:"equipment,omitempty"` + Comment []Comment `gorm:"foreignKey:item_id;references:id" json:"comment,omitempty"` + Tag []Tag `gorm:"foreignKey:item_id;references:id" json:"tag,omitempty"` + Ownership []Ownership `gorm:"foreignKey:item_id;references:id" json:"ownership,omitempty"` + Like []Like `gorm:"foreignKey:item_id;references:id" json:"like,omitempty"` + TransactionEquipment []TransactionEquipment `gorm:"foreignKey:item_id;references:id" json:"transactionEquipment,omitempty"` } func (Item) TableName() string { @@ -38,3 +44,157 @@ type Equipment struct { func (Equipment) TableName() string { return "equipments" } + +type RequestPostItemsBody struct { + Name string `json:"name"` + IsTrapItem bool `json:"isTrapItem"` + IsBook bool `json:"isBook"` + Count int `json:"count"` + Code string `json:"code"` + Tags []string `json:"tags"` + Description string `json:"description"` + ImgURL string `json:"imgUrl"` +} + +type GetItemsBody struct { + UserID string `json:"userId"` + Search string `json:"search"` + Rental string `json:"rental"` + Limit int `json:"limit"` + Offset int `json:"offset"` + Tags []string `json:"tag"` + TagsExclude []string `json:"tag-exclude"` + SortBy string `json:"sortby"` +} + +func dbPreloaded() *gorm.DB { + return db.Preload("Book").Preload("Equipment").Preload("Comment").Preload("Tag"). + Preload("Ownership").Preload("Like").Preload("Ownership.Transaction").Preload("TransactionEquipment") +} + +func GetItems(query GetItemsBody) ([]Item, error) { + query.Limit = max(query.Limit, 20) + + model := db.Preload("Book").Preload("Equipment").Preload("Tag") + model = model.Limit(query.Limit).Offset(query.Offset) + + if query.Search != "" { + model = model.Where("name LIKE ?", "%"+query.Search+"%") + } + + // TODO: userid, rental, tag, tag-exclude, sortby + // sortby: 項目名 + 降順or昇順 の2つの情報が必要そう + + items := []Item{} + if err := model.Find(&items).Error; err != nil { + return []Item{}, err + } + return items, nil +} + +func itemFromBody(itemBody RequestPostItemsBody) Item { + item := Item{ + Name: itemBody.Name, + Description: itemBody.Description, + ImgURL: itemBody.ImgURL, + Tag: stringsToTags(itemBody.Tags), + } + if itemBody.IsBook { + item.Book = &Book{Code: itemBody.Code} + } + if itemBody.IsTrapItem { + item.Equipment = &Equipment{Count: itemBody.Count, CountMax: itemBody.Count} + } + return item +} + +func CreateItems(itemBodies []RequestPostItemsBody, me string) ([]Item, error) { + items := make([]Item, len(itemBodies)) + err := db.Transaction(func(tx *gorm.DB) error { + for i, itemBody := range itemBodies { + items[i] = itemFromBody(itemBody) + + if items[i].Equipment == nil { + items[i].Ownership = []Ownership{{UserID: me, Rentalable: true}} + } + } + + if err := tx.Create(&items).Error; err != nil { + return err + } + + return nil + }) + + if err != nil { + return []Item{}, err + } + + return items, nil +} + +func stringsToTags(tagStrs []string) []Tag { + res := make([]Tag, len(tagStrs)) + for i, tagStr := range tagStrs { + res[i] = Tag{Name: tagStr} + } + return res +} + +func GetItem(itemID int) (Item, error) { + res := Item{} + if err := dbPreloaded().First(&res, itemID).Error; err != nil { + return Item{}, err + } + return res, nil +} + +// TODO: Booksの紐づけ, Ownershipsの除去/入力を適切に行う +// TODO: 備品がCount < CountMaxの際にPatchItemされたらどうする? +func PatchItem(itemID int, itemBody RequestPostItemsBody) (Item, error) { + itemOld, err := GetItem(itemID) + if err != nil { + return Item{}, err + } + + if itemOld.Book == nil && itemBody.IsBook || itemOld.Book != nil && !itemBody.IsBook { + return Item{}, errors.New("それが本かどうかの情報を変えることはできません") + } + if itemOld.Equipment == nil && itemBody.IsTrapItem || itemOld.Equipment != nil && !itemBody.IsTrapItem { + return Item{}, errors.New("それが物品かどうかの情報を変えることはできません") + } + + item := itemFromBody(itemBody) + item.Ownership = itemOld.Ownership + if err := db.Model(&itemOld).Updates(item).Error; err != nil { + return Item{}, err + } + + return item, nil +} + +func DeleteItem(itemID int) error { + item, err := GetItem(itemID) + if err != nil { + return err + } + + err = db.Transaction(func(tx *gorm.DB) error { + if item.Ownership != nil { + ownershipIDs := []int{} + if err := tx.Model(&Ownership{}).Where("item_id = ?", item.ID).Pluck("id", &ownershipIDs).Error; err != nil { + return err + } + if err := db.Where("ownership_id IN ?", ownershipIDs).Delete(&Transaction{}).Error; err != nil { + return err + } + } + if err := tx.Select("Book", "Equipment", "Comment", "Tag", "Like", "Ownership", + "TransactionEquipment").Delete(&item).Error; err != nil { + return err + } + return nil + }) + + return err +} diff --git a/model/items_test.go b/model/items_test.go index 8b53790..9545aeb 100644 --- a/model/items_test.go +++ b/model/items_test.go @@ -1 +1,164 @@ package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestItemTableName(t *testing.T) { + t.Parallel() + assert.Equal(t, "items", (&Item{}).TableName()) +} + +func TestGetItems(t *testing.T) { + PrepareTestDatabase() + + t.Run("success", func(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + res, err := GetItems(GetItemsBody{Search: "item-id1"}) + assert.NoError(err) + assert.NotEmpty(res) + assert.Equal(res[0].Name, "item-id1") + assert.Equal(res[0].Description, "aaa") + assert.Equal(res[0].ImgURL, "url") + assert.Empty(res[0].Book) + assert.Empty(res[0].Equipment) + }) +} + +func TestCreateItems(t *testing.T) { + PrepareTestDatabase() + + t.Run("failures", func(t *testing.T) { + assert := assert.New(t) + + items, err := CreateItems([]RequestPostItemsBody{}, "s9") + assert.Error(err) + assert.Empty(items) + }) + + t.Run("success", func(t *testing.T) { + assert := assert.New(t) + + items, err := CreateItems([]RequestPostItemsBody{ + {Name: "test1", IsTrapItem: false, IsBook: false, Tags: []string{"test_tag", "test_tag2"}, + Description: "test_description", ImgURL: "https://example.com/"}, + }, "s9") + assert.NoError(err) + assert.NotEmpty(items) + assert.NotEmpty(items[0].Ownership) + + items, err = CreateItems([]RequestPostItemsBody{ + {Name: "test2", IsTrapItem: false, IsBook: true, Tags: []string{"test_tag", "test_tag2"}, + Description: "test_description", ImgURL: "https://example.com/", Code: "9784088725093"}, + {Name: "test3", IsTrapItem: true, IsBook: false, Tags: []string{"test_tag", "test_tag2"}, + Description: "test_description", ImgURL: "https://example.com/", Count: 3}, + {Name: "test4", IsTrapItem: true, IsBook: true, Tags: []string{"test_tag", "test_tag2"}, + Description: "test_description", ImgURL: "https://example.com/", Code: "9784088725093", Count: 3}, + }, "s9") + assert.NoError(err) + assert.NotEmpty(items) + + assert.NotEmpty(items[0].Book) + assert.Empty(items[0].Equipment) + assert.NotEmpty(items[0].Ownership) + + assert.Empty(items[1].Book) + assert.NotEmpty(items[1].Equipment) + assert.Empty(items[1].Ownership) + }) +} + +func TestGetItem(t *testing.T) { + PrepareTestDatabase() + t.Run("failure", func(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + res, err := GetItem(-1) + assert.Error(err) + assert.Empty(res) + }) + + t.Run("success", func(t *testing.T) { + t.Parallel() + assert := assert.New(t) + + res, err := GetItem(1) + assert.NoError(err) + assert.NotEmpty(res) + assert.Equal(res.Name, "item-id1") + }) +} + +func TestPatchItem(t *testing.T) { + PrepareTestDatabase() + + t.Run("failure", func(t *testing.T) { + assert := assert.New(t) + + res, err := PatchItem(-1, RequestPostItemsBody{}) + assert.Error(err) + assert.Empty(res) + }) + + t.Run("success-1", func(t *testing.T) { + assert := assert.New(t) + + req := RequestPostItemsBody{ + Name: "testPatchItem", IsTrapItem: false, IsBook: false, + Tags: []string{"tagTest"}, Description: "testPatchItem", ImgURL: "testURL", + } + res, err := PatchItem(1, req) + + assert.NoError(err) + assert.NotEmpty(res) + assert.Empty(res.Book) + assert.Empty(res.Equipment) + assert.Equal(res.Tag[0].Name, req.Tags[0]) + assert.Equal(res.Description, req.Description) + assert.Equal(res.ImgURL, req.ImgURL) + assert.NotEmpty(res.Ownership) + }) + + t.Run("success-2", func(t *testing.T) { + assert := assert.New(t) + + req := RequestPostItemsBody{ + Name: "testPatchItem", IsTrapItem: true, IsBook: true, Count: 123456, Code: "9784088725093", + Tags: []string{"tagTest"}, Description: "testPatchItem", ImgURL: "testURL", + } + res, err := PatchItem(4, req) + + assert.NoError(err) + assert.NotEmpty(res) + assert.Equal(res.Book.Code, req.Code) + assert.Equal(res.Equipment.Count, req.Count) + assert.Equal(res.Equipment.CountMax, req.Count) + assert.Equal(res.Tag[0].Name, req.Tags[0]) + assert.Equal(res.Description, req.Description) + assert.Equal(res.ImgURL, req.ImgURL) + }) +} + +func TestDeleteItem(t *testing.T) { + PrepareTestDatabase() + + t.Run("failure", func(t *testing.T) { + assert := assert.New(t) + err := DeleteItem(-1) + assert.Error(err) + }) + + t.Run("success", func(t *testing.T) { + assert := assert.New(t) + err := DeleteItem(1) + assert.NoError(err) + + err = DeleteItem(2) + assert.NoError(err) + }) +} diff --git a/model/likes.go b/model/likes.go index 4d90ca5..4e58757 100644 --- a/model/likes.go +++ b/model/likes.go @@ -2,8 +2,8 @@ package model type Like struct { GormModelWithoutID - ItemID int `primaryKey;gorm:"type:int;not null" json:"item_id"` - UserID string `primaryKey;gorm:"type:text;not null" json:"user_id"` + ItemID int `gorm:"primaryKey;type:int;not null" json:"item_id"` + UserID string `gorm:"primaryKey;type:varchar(32);not null" json:"user_id"` } func (Like) TableName() string { diff --git a/model/ownership.go b/model/ownership.go index 7d2b6ad..ad84675 100644 --- a/model/ownership.go +++ b/model/ownership.go @@ -2,8 +2,10 @@ package model type Ownership struct { GormModel - ItemID int `gorm:"type:int;not null" json:"name"` + ItemID int `gorm:"type:int;not null" json:"itemId"` UserID string `gorm:"type:varchar(32);not null" json:"userId"` + Rentalable bool `gorm:"type:boolean;not null" json:"rentalable"` + Memo string `gorm:"type:varchar(32)" json:"memo"` Transaction []Transaction `gorm:"foreignKey:ownership_id;references:id"` } diff --git a/model/transactions.go b/model/transactions.go index bb6a306..e9ae95e 100644 --- a/model/transactions.go +++ b/model/transactions.go @@ -6,7 +6,7 @@ type Transaction struct { GormModel OwnershipID int `gorm:"type:int;not null" json:"ownershipId"` UserID string `gorm:"type:varchar(32);not null" json:"userId"` - Statue int `gorm:"type:int;not null" json:"statue"` + Status int `gorm:"type:int;not null" json:"status"` Purpose string `gorm:"type:text" json:"purpose"` Message string `gorm:"type:text" json:"message"` ReturnMessage string `gorm:"type:text" json:"returnMessage"` @@ -23,7 +23,7 @@ type TransactionEquipment struct { GormModel ItemID int `gorm:"type:int;not null" json:"itemId"` UserID string `gorm:"type:varchar(32);not null" json:"userId"` - Statue int `gorm:"type:int;not null" json:"statue"` + Status int `gorm:"type:int;not null" json:"status"` Purpose string `gorm:"type:text" json:"purpose"` ReturnMessage string `gorm:"type:text" json:"returnMessage"` ReturnDue time.Time `gorm:"type:datetime" json:"dueDate"` diff --git a/router/comments_test.go b/router/comments_test.go deleted file mode 100644 index 7ef135b..0000000 --- a/router/comments_test.go +++ /dev/null @@ -1 +0,0 @@ -package router diff --git a/router/files_test.go b/router/files_test.go deleted file mode 100644 index 7ef135b..0000000 --- a/router/files_test.go +++ /dev/null @@ -1 +0,0 @@ -package router diff --git a/router/item_test.go b/router/item_test.go new file mode 100644 index 0000000..e542ed8 --- /dev/null +++ b/router/item_test.go @@ -0,0 +1,326 @@ +package router + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + "github.com/traPtitech/booQ-v3/model" +) + +func TestGetItems(t *testing.T) { + model.PrepareTestDatabase() + + e := echo.New() + SetupRouting(e, CreateUserProvider("s9")) + + t.Run("failure", func(t *testing.T) { + t.Parallel() + assert := assert.New(t) + req := httptest.NewRequest(echo.GET, "/api/items?limit=aaa", nil) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(http.StatusBadRequest, rec.Code) + }) + t.Run("success-1", func(t *testing.T) { + t.Parallel() + assert := assert.New(t) + req := httptest.NewRequest(echo.GET, "/api/items?search=item-id4", nil) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(http.StatusOK, rec.Code) + + items := []model.Item{} + _ = json.NewDecoder(rec.Body).Decode(&items) + + assert.Equal("item-id4 book equipment", items[0].Name) + assert.Equal("aaa", items[0].Description) + assert.Equal("url", items[0].ImgURL) + assert.Equal(90, items[0].Equipment.Count) + assert.Equal(100, items[0].Equipment.CountMax) + assert.Equal("9784088725093", items[0].Book.Code) + assert.Equal("tag3", items[0].Tag[0].Name) + // 他の情報は/itemsではなく/items/{id}で取得させる + }) +} + +func TestPostItems(t *testing.T) { + model.PrepareTestDatabase() + + e := echo.New() + SetupRouting(e, CreateUserProvider("s9")) + + t.Run("success", func(t *testing.T) { + assert := assert.New(t) + + reqStruct := []model.RequestPostItemsBody{ + {Name: "item-id5", IsTrapItem: false, IsBook: false, Tags: []string{"tagtest"}, + Description: "bbb", ImgURL: "url"}, + {Name: "item-id6", IsTrapItem: true, IsBook: true, Tags: []string{"tagtest"}, + Description: "bbb", ImgURL: "url", Count: 100, Code: "9784041026403"}, + } + + reqBody, _ := json.Marshal(reqStruct) + req := httptest.NewRequest(echo.POST, "/api/items", bytes.NewReader(reqBody)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(http.StatusOK, rec.Code) + + items := []model.Item{} + _ = json.NewDecoder(rec.Body).Decode(&items) + + assert.Equal(reqStruct[0].Name, items[0].Name) + assert.Empty(items[0].Book) + assert.Empty(items[0].Equipment) + assert.Equal(reqStruct[0].Description, items[0].Description) + assert.Equal(reqStruct[0].ImgURL, items[0].ImgURL) + assert.Equal("s9", items[0].Ownership[0].UserID) + + assert.Equal(reqStruct[1].Name, items[1].Name) + assert.Equal(reqStruct[1].Code, items[1].Book.Code) + assert.Equal(reqStruct[1].Count, items[1].Equipment.Count) + assert.Equal(reqStruct[1].Count, items[1].Equipment.CountMax) + assert.Equal(reqStruct[1].Description, items[1].Description) + assert.Equal(reqStruct[1].ImgURL, items[1].ImgURL) + assert.Empty(items[1].Ownership) + }) +} + +func TestGetItem(t *testing.T) { + model.PrepareTestDatabase() + + e := echo.New() + SetupRouting(e, CreateUserProvider("s9")) + + t.Run("failure-1", func(t *testing.T) { + t.Parallel() + assert := assert.New(t) + req := httptest.NewRequest(echo.GET, "/api/items/-1", nil) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + assert.Equal(http.StatusInternalServerError, rec.Code) + }) + + t.Run("failure-2", func(t *testing.T) { + t.Parallel() + assert := assert.New(t) + req := httptest.NewRequest(echo.GET, "/api/items/aaa", nil) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + assert.Equal(http.StatusBadRequest, rec.Code) + }) + + t.Run("success-1", func(t *testing.T) { + t.Parallel() + assert := assert.New(t) + req := httptest.NewRequest(echo.GET, "/api/items/1", nil) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(http.StatusOK, rec.Code) + + item := model.Item{} + _ = json.NewDecoder(rec.Body).Decode(&item) + + assert.Equal("item-id1", item.Name) + assert.Equal("aaa", item.Description) + assert.Equal("url", item.ImgURL) + assert.Empty(item.Equipment) + assert.Empty(item.Book) + assert.Equal("tag1", item.Tag[0].Name) + assert.Empty(item.TransactionEquipment) + assert.Equal("s9", item.Comment[0].UserID) + assert.Equal("comment", item.Comment[0].Comment) + assert.Equal("cp20", item.Like[0].UserID) + assert.Equal("s9", item.Ownership[0].UserID) + assert.Equal(true, item.Ownership[0].Rentalable) + assert.Equal("memo1", item.Ownership[0].Memo) + assert.Equal("ryoha", item.Ownership[0].Transaction[2].UserID) + assert.Equal("かりたいから", item.Ownership[0].Transaction[2].Purpose) + assert.Equal("いいよ", item.Ownership[0].Transaction[2].Message) + assert.Equal("ありがとう", item.Ownership[0].Transaction[2].ReturnMessage) + }) + + t.Run("success-2", func(t *testing.T) { + t.Parallel() + assert := assert.New(t) + req := httptest.NewRequest(echo.GET, "/api/items/4", nil) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(http.StatusOK, rec.Code) + + item := model.Item{} + _ = json.NewDecoder(rec.Body).Decode(&item) + + assert.Equal("item-id4 book equipment", item.Name) + assert.Equal("aaa", item.Description) + assert.Equal("url", item.ImgURL) + assert.Equal(90, item.Equipment.Count) + assert.Equal(100, item.Equipment.CountMax) + assert.Equal("9784088725093", item.Book.Code) + assert.Equal("tag3", item.Tag[0].Name) + assert.Equal("ryoha", item.TransactionEquipment[0].UserID) + assert.Equal("かりたいから", item.TransactionEquipment[0].Purpose) + assert.Equal("かえしました", item.TransactionEquipment[0].ReturnMessage) + }) +} + +func TestPatchItem(t *testing.T) { + model.PrepareTestDatabase() + + e := echo.New() + SetupRouting(e, CreateUserProvider("s9")) + + t.Run("failure-1", func(t *testing.T) { + assert := assert.New(t) + + reqStruct := model.RequestPostItemsBody{ + Name: "item-id5", IsTrapItem: false, IsBook: false, Tags: []string{"tagtest"}, + Description: "bbb", ImgURL: "url"} + + reqBody, _ := json.Marshal(reqStruct) + req := httptest.NewRequest(echo.PATCH, "/api/items/4", bytes.NewReader(reqBody)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(http.StatusInternalServerError, rec.Code) + }) + + t.Run("failure-2", func(t *testing.T) { + assert := assert.New(t) + + reqStruct := model.RequestPostItemsBody{ + Name: "item-id6", IsTrapItem: true, IsBook: true, Tags: []string{"tagtest"}, + Description: "bbb", ImgURL: "url", Count: 100, Code: "9784041026403"} + + reqBody, _ := json.Marshal(reqStruct) + req := httptest.NewRequest(echo.PATCH, "/api/items/1", bytes.NewReader(reqBody)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(http.StatusInternalServerError, rec.Code) + }) + + t.Run("success-1", func(t *testing.T) { + assert := assert.New(t) + + reqStruct := model.RequestPostItemsBody{ + Name: "item-id5", IsTrapItem: false, IsBook: false, Tags: []string{"tagtest"}, + Description: "bbb", ImgURL: "url"} + + reqBody, _ := json.Marshal(reqStruct) + req := httptest.NewRequest(echo.PATCH, "/api/items/1", bytes.NewReader(reqBody)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(http.StatusOK, rec.Code) + + item := model.Item{} + _ = json.NewDecoder(rec.Body).Decode(&item) + + assert.Equal(reqStruct.Name, item.Name) + assert.Empty(item.Book) + assert.Empty(item.Equipment) + assert.Equal(reqStruct.Description, item.Description) + assert.Equal(reqStruct.ImgURL, item.ImgURL) + assert.Equal("s9", item.Ownership[0].UserID) + }) + + t.Run("success-2", func(t *testing.T) { + assert := assert.New(t) + + reqStruct := model.RequestPostItemsBody{ + Name: "item-id6", IsTrapItem: true, IsBook: true, Tags: []string{"tagtest"}, + Description: "bbb", ImgURL: "url", Count: 111, Code: "9784123457689"} + + reqBody, _ := json.Marshal(reqStruct) + req := httptest.NewRequest(echo.PATCH, "/api/items/4", bytes.NewReader(reqBody)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(http.StatusOK, rec.Code) + + item := model.Item{} + _ = json.NewDecoder(rec.Body).Decode(&item) + + assert.Equal(reqStruct.Name, item.Name) + assert.Equal("9784123457689", item.Book.Code) + assert.Equal(111, item.Equipment.Count) + assert.Equal(111, item.Equipment.CountMax) + assert.Equal(reqStruct.Description, item.Description) + assert.Equal(reqStruct.ImgURL, item.ImgURL) + assert.Empty(item.Ownership) + }) +} + +func TestDeleteItem(t *testing.T) { + model.PrepareTestDatabase() + + e := echo.New() + SetupRouting(e, CreateUserProvider("s9")) + + t.Run("failure-1", func(t *testing.T) { + assert := assert.New(t) + req := httptest.NewRequest(echo.DELETE, "/api/items/-1", nil) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + assert.Equal(http.StatusInternalServerError, rec.Code) + }) + + t.Run("failure-2", func(t *testing.T) { + assert := assert.New(t) + req := httptest.NewRequest(echo.DELETE, "/api/items/aaa", nil) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + assert.Equal(http.StatusBadRequest, rec.Code) + }) + + t.Run("success-1", func(t *testing.T) { + assert := assert.New(t) + req := httptest.NewRequest(echo.DELETE, "/api/items/1", nil) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(http.StatusOK, rec.Code) + + item, err := model.GetItem(1) + assert.Error(err) + assert.Empty(item) + }) + + t.Run("success-2", func(t *testing.T) { + assert := assert.New(t) + req := httptest.NewRequest(echo.DELETE, "/api/items/4", nil) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + assert.Equal(http.StatusOK, rec.Code) + + item, err := model.GetItem(4) + assert.Error(err) + assert.Empty(item) + }) +} diff --git a/router/items.go b/router/items.go index b77a543..65e744d 100644 --- a/router/items.go +++ b/router/items.go @@ -2,31 +2,139 @@ package router import ( "net/http" + "strconv" "github.com/labstack/echo/v4" + "github.com/traPtitech/booQ-v3/model" ) // GetItems GET /items func GetItems(c echo.Context) error { - return echo.NewHTTPError(http.StatusNotImplemented, "Not Implemented") + getItemsBody, err := getItemsParams(c) + if err != nil { + c.Logger().Info(err.Error()) + return c.JSON(http.StatusBadRequest, "リクエストデータの処理に失敗しました") + } + + res, err := model.GetItems(getItemsBody) + if err != nil { + c.Logger().Info(err.Error()) + return c.JSON(http.StatusInternalServerError, "DBの操作に失敗しました") + } + + return c.JSON(http.StatusOK, res) +} + +func getItemsParams(c echo.Context) (model.GetItemsBody, error) { + params := c.QueryParams() + + getItemsBody := model.GetItemsBody{ + UserID: params.Get("userId"), + Search: params.Get("search"), + Rental: params.Get("rental"), + Tags: params["tag"], + TagsExclude: params["tag-exclude"], + SortBy: params.Get("sortby"), + } + + if params.Get("limit") != "" { + limit, err := strconv.Atoi(params.Get("limit")) + if err != nil { + return model.GetItemsBody{}, err + } + getItemsBody.Limit = limit + } + + if params.Get("offset") != "" { + offset, err := strconv.Atoi(params.Get("offset")) + if err != nil { + return model.GetItemsBody{}, err + } + getItemsBody.Offset = offset + } + + c.Logger().Info(getItemsBody) + + return getItemsBody, nil } // PostItems POST /items func PostItems(c echo.Context) error { - return echo.NewHTTPError(http.StatusNotImplemented, "Not Implemented") + me := c.Get("user").(string) + items := []model.RequestPostItemsBody{} + err := c.Bind(&items) + if err != nil { + c.Logger().Info(err.Error()) + return c.JSON(http.StatusBadRequest, "リクエストデータの処理に失敗しました") + } + + res, err := model.CreateItems(items, me) + if err != nil { + c.Logger().Info(err.Error()) + return c.JSON(http.StatusBadRequest, "DBの操作に失敗しました") + } + + return c.JSON(http.StatusOK, res) } // GetItem GET /items/:id func GetItem(c echo.Context) error { - return echo.NewHTTPError(http.StatusNotImplemented, "Not Implemented") + itemIDRaw := c.Param("id") + + itemID, err := strconv.Atoi(itemIDRaw) + if err != nil { + c.Logger().Info(err.Error()) + return c.JSON(http.StatusBadRequest, "物品のIDが不正です") + } + + res, err := model.GetItem(itemID) + if err != nil { + c.Logger().Info(err.Error()) + return c.JSON(http.StatusInternalServerError, "DBの操作に失敗しました") + } + + return c.JSON(http.StatusOK, res) } -// PutItem PUT /items/:id -func PutItem(c echo.Context) error { - return echo.NewHTTPError(http.StatusNotImplemented, "Not Implemented") +// PatchItem PUT /items/:id +func PatchItem(c echo.Context) error { + itemBody := model.RequestPostItemsBody{} + err := c.Bind(&itemBody) + if err != nil { + c.Logger().Info(err.Error()) + return c.JSON(http.StatusBadRequest, "リクエストデータの処理に失敗しました") + } + + itemIDRaw := c.Param("id") + itemID, err := strconv.Atoi(itemIDRaw) + if err != nil { + c.Logger().Info(err.Error()) + return c.JSON(http.StatusBadRequest, "物品のIDが不正です") + } + + res, err := model.PatchItem(itemID, itemBody) + if err != nil { + c.Logger().Info(err.Error()) + return c.JSON(http.StatusInternalServerError, "DBの操作に失敗しました") + } + + return c.JSON(http.StatusOK, res) } // DeleteItem DELETE /items/:id func DeleteItem(c echo.Context) error { - return echo.NewHTTPError(http.StatusNotImplemented, "Not Implemented") + itemIDRaw := c.Param("id") + itemID, err := strconv.Atoi(itemIDRaw) + if err != nil { + c.Logger().Info(err.Error()) + return c.JSON(http.StatusBadRequest, "物品のIDが不正です") + } + + err = model.DeleteItem(itemID) + if err != nil { + c.Logger().Info(err.Error()) + return c.JSON(http.StatusInternalServerError, "DBの操作に失敗しました") + } + + return c.NoContent(http.StatusOK) } diff --git a/router/items_test.go b/router/items_test.go deleted file mode 100644 index 7ef135b..0000000 --- a/router/items_test.go +++ /dev/null @@ -1 +0,0 @@ -package router diff --git a/router/router.go b/router/router.go index 96e3cfc..be98ad2 100644 --- a/router/router.go +++ b/router/router.go @@ -21,7 +21,7 @@ func SetupRouting(e *echo.Echo, client *UserProvider) { apiItems.GET("", GetItems) apiItems.POST("", PostItems) apiItems.GET("/:id", GetItem) - apiItems.PUT("/:id", PutItem) + apiItems.PATCH("/:id", PatchItem) apiItems.DELETE("/:id", DeleteItem) apiItems.POST("/:id/owners", PostOwners) diff --git a/router/router_test.go b/router/router_test.go index 7ef135b..3d5debd 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -1 +1,14 @@ package router + +import ( + "os" + "testing" + + "github.com/traPtitech/booQ-v3/model" +) + +func TestMain(m *testing.M) { + model.SetUpTestDB() + exitCode := m.Run() + os.Exit(exitCode) +} diff --git a/testdata/fixtures/books.yml b/testdata/fixtures/books.yml new file mode 100644 index 0000000..2539708 --- /dev/null +++ b/testdata/fixtures/books.yml @@ -0,0 +1,5 @@ +- item_id: 2 + code: 9784088725093 +- item_id: 4 + code: 9784088725093 + diff --git a/testdata/fixtures/comments.yml b/testdata/fixtures/comments.yml new file mode 100644 index 0000000..c09a544 --- /dev/null +++ b/testdata/fixtures/comments.yml @@ -0,0 +1,13 @@ +- id: 1 + item_id: 1 + user_id: "s9" + comment: "comment" +- id: 2 + item_id: 1 + user_id: "cp20" + comment: "comment" +- id: 3 + item_id: 2 + user_id: "ryoha" + comment: "comment" + diff --git a/testdata/fixtures/equipments.yml b/testdata/fixtures/equipments.yml new file mode 100644 index 0000000..68dc63d --- /dev/null +++ b/testdata/fixtures/equipments.yml @@ -0,0 +1,7 @@ +- item_id: 3 + count: 90 + count_max: 100 +- item_id: 4 + count: 90 + count_max: 100 + diff --git a/testdata/fixtures/items.yml b/testdata/fixtures/items.yml new file mode 100644 index 0000000..24284a5 --- /dev/null +++ b/testdata/fixtures/items.yml @@ -0,0 +1,17 @@ +- id: 1 + name: "item-id1" + description: "aaa" + img_url: "url" +- id: 2 + name: "item-id2 book" + description: "aaa" + img_url: "url" +- id: 3 + name: "item-id3 equipment" + description: "aaa" + img_url: "url" +- id: 4 + name: "item-id4 book equipment" + description: "aaa" + img_url: "url" + diff --git a/testdata/fixtures/likes.yml b/testdata/fixtures/likes.yml new file mode 100644 index 0000000..1d9b4bd --- /dev/null +++ b/testdata/fixtures/likes.yml @@ -0,0 +1,9 @@ +- item_id: 1 + user_id: "s9" +- item_id: 2 + user_id: "s9" +- item_id: 1 + user_id: "cp20" +- item_id: 1 + user_id: "takku_bobshiroshiro_titech_trap" + diff --git a/testdata/fixtures/ownerships.yml b/testdata/fixtures/ownerships.yml new file mode 100644 index 0000000..1ad06ef --- /dev/null +++ b/testdata/fixtures/ownerships.yml @@ -0,0 +1,16 @@ +- id: 1 + item_id: 1 + user_id: "s9" + rentalable: true + memo: "memo1" +- id: 2 + item_id: 1 + user_id: "cp20" + rentalable: true + memo: "memo2" +- id: 3 + item_id: 1 + user_id: "s9" + rentalable: false + memo: "memo3" + diff --git a/testdata/fixtures/tags.yml b/testdata/fixtures/tags.yml new file mode 100644 index 0000000..d76a05a --- /dev/null +++ b/testdata/fixtures/tags.yml @@ -0,0 +1,11 @@ +- name: "tag1" + item_id: 1 +- name: "tag2" + item_id: 1 +- name: "tag2" + item_id: 2 +- name: "tag2" + item_id: 3 +- name: "tag3" + item_id: 4 + diff --git a/testdata/fixtures/transactions.yml b/testdata/fixtures/transactions.yml new file mode 100644 index 0000000..b638d7e --- /dev/null +++ b/testdata/fixtures/transactions.yml @@ -0,0 +1,25 @@ +- id: 1 + ownership_id: 1 + user_id: "ryoha" + status: 0 + purpose: "かりたいから" +- id: 2 + ownership_id: 1 + user_id: "ryoha" + status: 1 + purpose: "かりたいから" + message: "いいよ" +- id: 3 + ownership_id: 1 + user_id: "ryoha" + status: 2 + purpose: "かりたいから" + message: "いいよ" + return_message: "ありがとう" +- id: 4 + ownership_id: 1 + user_id: "ryoha" + status: 3 + purpose: "かりたいから" + message: "ごめん、いまむり" + diff --git a/testdata/fixtures/transactions_equipment.yml b/testdata/fixtures/transactions_equipment.yml new file mode 100644 index 0000000..a568110 --- /dev/null +++ b/testdata/fixtures/transactions_equipment.yml @@ -0,0 +1,18 @@ +- id: 1 + item_id: 3 + user_id: "ryoha" + status: 0 + purpose: "かりたいから" +- id: 2 + item_id: 3 + user_id: "ryoha" + status: 1 + purpose: "かりたいから" + return_message: "かえしました" +- id: 3 + item_id: 4 + user_id: "ryoha" + status: 1 + purpose: "かりたいから" + return_message: "かえしました" +