Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .gitignore
Empty file.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Build@Mercari Training Program

This is @<your github id>'s build training repository.
This is @maru65536's build training repository.

Build trainingの前半では個人で課題に取り組んでもらい、Web開発の基礎知識をつけていただきます。
ドキュメントには詳細なやり方は記載しません。自身で検索したり、リファレンスを確認したり、チームメイトと協力して各課題をクリアしましょう。
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module mercari-build-training

go 1.24.1
Empty file added go.sum
Empty file.
54 changes: 49 additions & 5 deletions go/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,52 @@
FROM alpine
# 使用するGoのイメージを指定
FROM golang:1.24-alpine AS builder

RUN addgroup -S mercari && adduser -S trainee -G mercari
# RUN chown -R trainee:mercari /path/to/db
# 環境変数を設定
ENV GO111MODULE=on
ENV GOPATH=/go

USER trainee
# 作業ディレクトリを指定
WORKDIR /app

# 必要なGoモジュールをインストール
COPY go.mod go.sum ./
RUN go mod tidy

# 依存関係のパッケージをダウンロード
RUN go get github.com/mattn/go-sqlite3 go.uber.org/mock/gomock

# プロジェクトのソースコードをコピー
# 必要なファイルだけ正しい場所にコピー
COPY go/cmd/api /app/cmd/api
COPY go/app /app/app
COPY go/db /app/db
COPY go/items.json /app/items.json

# ビルド
RUN go build -o main /app/cmd/api/main.go

# 軽量な実行用のイメージを作成
FROM alpine:latest

# 必要なライブラリをインストール
# Dockerfileの途中でインストール結果を確認
RUN apk update --no-cache && apk add --no-cache sqlite && sqlite3 --version


# 作業ディレクトリを /root に設定
WORKDIR /root

# ビルドしたバイナリをコンテナにコピー
COPY --from=builder /app/main .
COPY go/items.json ./items.json
COPY go/db ./db

# ポート9000を開放
EXPOSE 9000

# 実行時に main バイナリを実行
CMD ["./main"]

# コンテナ内での画像保存場所を指定
VOLUME ["/app/images"]

CMD ["go", "version"]
190 changes: 179 additions & 11 deletions go/app/infra.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,30 @@ package app

import (
"context"
"database/sql"
"encoding/json"
"errors"
"log"
"log/slog"
"os"
// STEP 5-1: uncomment this line
// _ "github.com/mattn/go-sqlite3"
_ "github.com/mattn/go-sqlite3"
)

var errImageNotFound = errors.New("image not found")
var (
errImageNotFound = errors.New("image not found")
errItemsNotFound = errors.New("item not found")
)

type Items struct {
Items []*Item `json:"items"`
}

type Item struct {
ID int `db:"id" json:"-"`
Name string `db:"name" json:"name"`
ID int `db:"id" json:"-"`
Name string `db:"name" json:"name"`
Category string `db:"category" json:"category"`
ImageName string `db:"image_name" json:"image_name"`
}

// Please run `go generate ./...` to generate the mock implementation
Expand All @@ -20,30 +34,184 @@ type Item struct {
//go:generate go run go.uber.org/mock/mockgen -source=$GOFILE -package=${GOPACKAGE} -destination=./mock_$GOFILE
type ItemRepository interface {
Insert(ctx context.Context, item *Item) error
SelectAll(ctx context.Context) ([]*Item, error)
GetItem(ctx context.Context, id int) (*Item, error)
SearchFromName(ctx context.Context, name string) ([]*Item, error)
}

// itemRepository is an implementation of ItemRepository
type itemRepository struct {
dbPath string
// fileName is the path to the JSON file storing items.
fileName string
}

// NewItemRepository creates a new itemRepository.
func NewItemRepository() ItemRepository {
return &itemRepository{fileName: "items.json"}
return &itemRepository{
dbPath: "/mnt/data/mercari.sqlite3", // ボリュームのパス
// fileName: "items.json"
}
}

// Insert inserts an item into the repository.
func (i *itemRepository) Insert(ctx context.Context, item *Item) error {
// STEP 4-1: add an implementation to store an item
if i.fileName != "" {
return i.insertToFile(ctx, item)
}
db, err := sql.Open("sqlite3", i.dbPath)
if err != nil {
return err
}
defer db.Close()

return nil
_, err = db.Exec(
"INSERT INTO item (name, category, image_name) VALUES (?, ?, ?)",
item.Name, item.Category, item.ImageName,
)
return err
}

func (i *itemRepository) GetItem(ctx context.Context, id int) (*Item, error) {
if i.fileName != "" {
items, err := i.SelectAll(ctx)
if err != nil {
return nil, err
}
if items == nil || len(items) <= id {
return nil, err
}
return items[id], nil
}

db, err := sql.Open("sqlite3", i.dbPath)
if err != nil {
return nil, err
}
defer db.Close()

var item Item
err = db.QueryRow("SELECT id, name, category, image_name FROM item WHERE id = ?", id).Scan(&item.ID, &item.Name, &item.Category, &item.ImageName)
if err != nil {
return nil, err
}
return &item, nil
}

func (i *itemRepository) SelectAll(ctx context.Context) ([]*Item, error) {
// Added this to leave the code for the JSON implementation.

if i.fileName != "" {
items, err := i.getItemsFromFile(ctx)
return items, err
}

db, err := sql.Open("sqlite3", i.dbPath)
if err != nil {
return nil, err
}
defer db.Close()

rows, err := db.Query("SELECT id, name, category, image_name FROM item")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
items := []*Item{}
for rows.Next() {
var item Item
if err := rows.Scan(&item.ID, &item.Name, &item.Category, &item.ImageName); err != nil {
log.Fatal(err)
}
items = append(items, &item)
}
if err := rows.Err(); err != nil {
return nil, err
}

return items, nil
}

// StoreImage stores an image and returns an error if any.
// This package doesn't have a related interface for simplicity.
func StoreImage(fileName string, image []byte) error {
// STEP 4-4: add an implementation to store an image
func (i *itemRepository) SearchFromName(ctx context.Context, name string) ([]*Item, error) {
db, err := sql.Open("sqlite3", i.dbPath)
if err != nil {
return nil, err
}
defer db.Close()

rows, err := db.Query("SELECT id, name, category, image_name FROM item where name like ?", "%"+name+"%")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
items := []*Item{}
for rows.Next() {
var item Item
if err := rows.Scan(&item.ID, &item.Name, &item.Category, &item.ImageName); err != nil {
log.Fatal(err)
}
items = append(items, &item)
}
if err := rows.Err(); err != nil {
return nil, err
}

return items, nil
}

func (i *itemRepository) getItemsFromFile(ctx context.Context) ([]*Item, error) {
var items Items
if _, err := os.Stat(i.fileName); err == nil {
// File exists, open it for reading
f, err := os.Open(i.fileName)
if err != nil {
return nil, err
}
defer f.Close()
// Decode existing items from the file
if err := json.NewDecoder(f).Decode(&items); err != nil {
return nil, err
}
} else if os.IsNotExist(err) {
// File does not exist, initialize items list
items.Items = []*Item{}
} else {
// Some other error occurred
return nil, err
}
return items.Items, nil
}

func (i *itemRepository) insertToFile(ctx context.Context, item *Item) error {
items, err := i.getItemsFromFile(ctx)
if err != nil {
return err
}
slog.Info("items before insert", "items", items)
// Append the new item
items = append(items, item)
newItems := Items{Items: items}
// Marshal items to JSON
b, err := json.Marshal(newItems)
if err != nil {
return err
}
// Open or create the file for writing
f, err := os.Create(i.fileName)
if err != nil {
return err
}
defer f.Close()
// Write the JSON data to the file
_, err = f.Write(b)
if err != nil {
return err
}
slog.Info("items after insert", "items", items)
return nil
}

func StoreImage(filePath string, image []byte) error {
return os.WriteFile(filePath, image, 0644)
}

45 changes: 45 additions & 0 deletions go/app/mock_infra.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading