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
10 changes: 6 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@ WORKDIR /app

COPY . .

RUN make build
RUN go mod download
RUN go build -o main app/main.go

# Distribution
FROM alpine:latest

RUN apk update && apk upgrade && \
apk --update --no-cache add tzdata && \
apk --update --no-cache add tzdata ca-certificates && \
mkdir /app

WORKDIR /app

EXPOSE 9090

COPY --from=builder /app/engine /app/
COPY --from=builder /app/main /app/
COPY --from=builder /app/.env /app/

CMD /app/engine
CMD ["./main"]
8 changes: 6 additions & 2 deletions app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
mysqlRepo "github.com/bxcodec/go-clean-arch/internal/repository/mysql"

"github.com/bxcodec/go-clean-arch/article"
"github.com/bxcodec/go-clean-arch/user"
"github.com/bxcodec/go-clean-arch/internal/rest"
"github.com/bxcodec/go-clean-arch/internal/rest/middleware"
"github.com/joho/godotenv"
Expand Down Expand Up @@ -75,10 +76,13 @@ func main() {
// Prepare Repository
authorRepo := mysqlRepo.NewAuthorRepository(dbConn)
articleRepo := mysqlRepo.NewArticleRepository(dbConn)
userRepo := mysqlRepo.NewUserRepository(dbConn)

// Build service Layer
svc := article.NewService(articleRepo, authorRepo)
rest.NewArticleHandler(e, svc)
articleSvc := article.NewService(articleRepo, authorRepo)
userSvc := user.NewService(userRepo)
rest.NewArticleHandler(e, articleSvc)
rest.NewUserHandler(e, userSvc)

// Start Server
address := os.Getenv("SERVER_ADDRESS")
Expand Down
15 changes: 11 additions & 4 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
version: "3.7"
services:
web:
image: go-clean-arch
container_name: article_management_api
build: .
container_name: go_clean_arch_api
ports:
- 9090:9090
depends_on:
mysql:
condition: service_healthy
volumes:
- ./config.json:/app/config.json
environment:
- DATABASE_HOST=mysql
- DATABASE_PORT=3306
- DATABASE_USER=user
- DATABASE_PASS=password
- DATABASE_NAME=article
- SERVER_ADDRESS=:9090
- CONTEXT_TIMEOUT=30

mysql:
image: mysql:8.3
container_name: go_clean_arch_mysql
command: mysqld --user=root
volumes:
- ./article.sql:/docker-entrypoint-initdb.d/init.sql
- ./user.sql:/docker-entrypoint-initdb.d/user.sql
ports:
- 3306:3306
environment:
Expand Down
28 changes: 28 additions & 0 deletions domain/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package domain

import (
"time"
)

// User representing the User data struct
type User struct {
ID int64 `json:"id"`
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password,omitempty" validate:"required,min=6"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
}

// UserLoginRequest representing the login request struct
type UserLoginRequest struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required"`
}

// UserRegisterRequest representing the register request struct
type UserRegisterRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=6"`
}
182 changes: 182 additions & 0 deletions internal/repository/mysql/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package mysql

import (
"context"
"database/sql"
"fmt"

"github.com/sirupsen/logrus"

"github.com/bxcodec/go-clean-arch/domain"
"github.com/bxcodec/go-clean-arch/internal/repository"
)

type UserRepository struct {
Conn *sql.DB
}

// NewUserRepository will create an object that represent the user.Repository interface
func NewUserRepository(conn *sql.DB) *UserRepository {
return &UserRepository{conn}
}

func (m *UserRepository) fetch(ctx context.Context, query string, args ...interface{}) (result []domain.User, err error) {
rows, err := m.Conn.QueryContext(ctx, query, args...)
if err != nil {
logrus.Error(err)
return nil, err
}

defer func() {
errRow := rows.Close()
if errRow != nil {
logrus.Error(errRow)
}
}()

result = make([]domain.User, 0)
for rows.Next() {
t := domain.User{}
err = rows.Scan(
&t.ID,
&t.Name,
&t.Email,
&t.Password,
&t.UpdatedAt,
&t.CreatedAt,
)

if err != nil {
logrus.Error(err)
return nil, err
}
result = append(result, t)
}

return result, nil
}

func (m *UserRepository) Fetch(ctx context.Context, cursor string, num int64) (res []domain.User, nextCursor string, err error) {
query := `SELECT id, name, email, password, updated_at, created_at
FROM users WHERE created_at > ? ORDER BY created_at LIMIT ? `

decodedCursor, err := repository.DecodeCursor(cursor)
if err != nil && cursor != "" {
return nil, "", domain.ErrBadParamInput
}

res, err = m.fetch(ctx, query, decodedCursor, num)
if err != nil {
return nil, "", err
}

if len(res) == int(num) {
nextCursor = repository.EncodeCursor(res[len(res)-1].CreatedAt)
}

return
}

func (m *UserRepository) GetByID(ctx context.Context, id int64) (res domain.User, err error) {
query := `SELECT id, name, email, password, updated_at, created_at
FROM users WHERE ID = ?`

list, err := m.fetch(ctx, query, id)
if err != nil {
return domain.User{}, err
}

if len(list) > 0 {
res = list[0]
} else {
return res, domain.ErrNotFound
}

return
}

func (m *UserRepository) GetByEmail(ctx context.Context, email string) (res domain.User, err error) {
query := `SELECT id, name, email, password, updated_at, created_at
FROM users WHERE email = ?`

list, err := m.fetch(ctx, query, email)
if err != nil {
return
}

if len(list) > 0 {
res = list[0]
} else {
return res, domain.ErrNotFound
}
return
}

func (m *UserRepository) Store(ctx context.Context, u *domain.User) (err error) {
query := `INSERT users SET name=?, email=?, password=?, updated_at=?, created_at=?`
stmt, err := m.Conn.PrepareContext(ctx, query)
if err != nil {
return
}

res, err := stmt.ExecContext(ctx, u.Name, u.Email, u.Password, u.UpdatedAt, u.CreatedAt)
if err != nil {
return
}
lastID, err := res.LastInsertId()
if err != nil {
return
}
u.ID = lastID
return
}

func (m *UserRepository) Delete(ctx context.Context, id int64) (err error) {
query := "DELETE FROM users WHERE id = ?"

stmt, err := m.Conn.PrepareContext(ctx, query)
if err != nil {
return
}

res, err := stmt.ExecContext(ctx, id)
if err != nil {
return
}

rowsAfected, err := res.RowsAffected()
if err != nil {
return
}

if rowsAfected != 1 {
err = fmt.Errorf("weird Behavior. Total Affected: %d", rowsAfected)
return
}

return
}

func (m *UserRepository) Update(ctx context.Context, u *domain.User) (err error) {
query := `UPDATE users set name=?, email=?, password=?, updated_at=? WHERE ID = ?`

stmt, err := m.Conn.PrepareContext(ctx, query)
if err != nil {
return
}

res, err := stmt.ExecContext(ctx, u.Name, u.Email, u.Password, u.UpdatedAt, u.ID)
if err != nil {
return
}
affect, err := res.RowsAffected()
if err != nil {
return
}
if affect != 1 {
err = fmt.Errorf("weird Behavior. Total Affected: %d", affect)
return
}

return
}
Loading