From 2673a1300f3db3c0aec150c433109691a07022c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=80=ED=98=84?= <102128060+wlgusqkr@users.noreply.github.com> Date: Thu, 18 Sep 2025 11:42:13 +0900 Subject: [PATCH 1/4] feat:create subscription, user, summary table --- internal/global/db/db.go | 91 +++++++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 11 deletions(-) diff --git a/internal/global/db/db.go b/internal/global/db/db.go index 3aefe07..29925d6 100644 --- a/internal/global/db/db.go +++ b/internal/global/db/db.go @@ -5,8 +5,6 @@ import ( "fmt" "time" - _ "github.com/lib/pq" // PostgreSQL 드라이버 (사용하지 않지만 import 필요) - "github.com/nicewook/gocore/internal/global/config" ) @@ -16,18 +14,17 @@ import ( func NewDBConnection(cfg *config.Config) (*sql.DB, error) { // DSN(Data Source Name) 생성 - 데이터베이스 연결 문자열 dsn := fmt.Sprintf( - "host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", - cfg.DB.Host, // 데이터베이스 호스트 주소 - cfg.DB.Port, // 데이터베이스 포트 번호 - cfg.DB.User, // 데이터베이스 사용자명 - cfg.DB.Password, // 데이터베이스 비밀번호 - cfg.DB.DBName, // 데이터베이스 이름 - cfg.DB.SSLMode, // SSL 연결 모드 (disable, require 등) + "%s:%s@tcp(%s:%d)/%s?parseTime=true", + cfg.DB.User, + cfg.DB.Password, + cfg.DB.Host, + cfg.DB.Port, + cfg.DB.DBName, ) - // PostgreSQL 데이터베이스 연결 생성 + // mysql 데이터베이스 연결 생성 // sql.Open은 실제 연결을 생성하지 않고 연결 가능한 객체만 반환 - db, err := sql.Open("postgres", dsn) + db, err := sql.Open("mysql", dsn) if err != nil { return nil, fmt.Errorf("데이터베이스 연결 생성 실패: %w", err) } @@ -37,6 +34,17 @@ func NewDBConnection(cfg *config.Config) (*sql.DB, error) { db.SetMaxIdleConns(5) // 유휴 상태로 유지할 연결 수: 5개 db.SetConnMaxLifetime(5 * time.Minute) // 연결의 최대 수명: 5분 (5분 후 재연결) + if err := createUserTable(db); err != nil { + return nil, fmt.Errorf("failed to create users table: %w", err) + } + + if err := createSubscriptionTable(db); err != nil { + return nil, fmt.Errorf("failed to create subscriptions table: %w", err) + } + + if err := createSummaryTable(db); err != nil { + return nil, fmt.Errorf("failed to create summary table: %w", err) + } // 실제 데이터베이스 연결 테스트 // Ping()을 통해 데이터베이스가 실제로 연결 가능한지 확인 if err := db.Ping(); err != nil { @@ -46,3 +54,64 @@ func NewDBConnection(cfg *config.Config) (*sql.DB, error) { // 연결 성공 시 데이터베이스 객체 반환 return db, nil } + +func createUserTable(db *sql.DB) error { + + const query = ` + CREATE TABLE IF NOT EXISTS user ( + id BIGINT NOT NULL AUTO_INCREMENT, + name VARCHAR(30) NOT NULL, + created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + updated_at DATETIME(6) DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY (id) + ) + ` + if _, err := db.Exec(query); err != nil { + return fmt.Errorf("failed to create User table: %w", err) + } + return nil +} + +func createSubscriptionTable(db *sql.DB) error { + + const query = ` + CREATE TABLE IF NOT EXISTS subscription ( + id BIGINT NOT NULL AUTO_INCREMENT, + user_id BIGINT NOT NULL, + url TEXT NOT NULL, + alias VARCHAR(30), + keyword_filter VARCHAR(100), + is_urgent BOOLEAN NOT NULL DEFAULT FALSE, + created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + updated_at DATETIME(6) DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY (id), + FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE + ) + ` + if _, err := db.Exec(query); err != nil { + return fmt.Errorf("failed to create products table: %w", err) + } + return nil +} + +func createSummaryTable(db *sql.DB) error { + + const query = ` + CREATE TABLE IF NOT EXISTS summary ( + id BIGINT NOT NULL AUTO_INCREMENT, + subscription_id BIGINT NOT NULL, + hash TEXT NOT NULL, + summary TEXT NOT NULL, + is_read BOOLEAN NOT NULL DEFAULT FALSE, + created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + updated_at DATETIME(6) DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY (id), + FOREIGN KEY (subscription_id) REFERENCES subscription(id) ON DELETE CASCADE + ) + ` + + if _, err := db.Exec(query); err != nil { + return fmt.Errorf("failed to create orders table: %w", err) + } + return nil +} From b2d73bf37366e8b89e5bdf05c3f8fba2b4c1e3f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=80=ED=98=84?= <102128060+wlgusqkr@users.noreply.github.com> Date: Thu, 18 Sep 2025 11:44:05 +0900 Subject: [PATCH 2/4] feat: Create Summary, Subscription, User Type --- internal/cmd/gocore/main.go | 6 ++++-- internal/domain/summary/summary.go | 14 ++++++++++++++ internal/domain/user/user.go | 11 +++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 internal/domain/summary/summary.go create mode 100644 internal/domain/user/user.go diff --git a/internal/cmd/gocore/main.go b/internal/cmd/gocore/main.go index 9293811..d3defb4 100644 --- a/internal/cmd/gocore/main.go +++ b/internal/cmd/gocore/main.go @@ -14,6 +14,8 @@ import ( "github.com/labstack/echo/v4" // 웹 프레임워크 (Spring의 @RestController 같은) "go.uber.org/fx" // 의존성 주입 (Spring의 @Autowired 같은) + _ "github.com/go-sql-driver/mysql" + // 우리 프로젝트의 패키지들 "github.com/nicewook/gocore/internal/global/config" // 설정 관리 "github.com/nicewook/gocore/internal/global/db" // 데이터베이스 연결 @@ -93,7 +95,7 @@ func NewLogger(cfg *config.Config) *slog.Logger { } func NewDB(lc fx.Lifecycle, cfg *config.Config) *sql.DB { - // 데이터베이스 연결 생성 + dbConn, err := db.NewDBConnection(cfg) if err != nil { log.Fatalf("DB connection error: %v", err) @@ -117,7 +119,7 @@ func RegisterMiddlewares(cfg *config.Config, logger *slog.Logger, e *echo.Echo) middlewares.RegisterMiddlewares(cfg, logger, e) } -func RegisterRoutes(e *echo.Echo) { +func RegisterRoutes(e *echo.Echo, db *sql.DB) { // 헬스체크 엔드포인트 e.GET("/health", func(c echo.Context) error { return errors.OK(c, map[string]string{ diff --git a/internal/domain/summary/summary.go b/internal/domain/summary/summary.go new file mode 100644 index 0000000..abd4da2 --- /dev/null +++ b/internal/domain/summary/summary.go @@ -0,0 +1,14 @@ +package summary + +import "time" + +// Summary struct는 데이터베이스의 summary 테이블과 매핑되는 엔티티입니다. +type Summary struct { + ID int64 `json:"id"` + SubscriptionID int64 `json:"subscription_id" validate:"required,gt=0"` + Hash string `json:"hash" validate:"required"` + Summary string `json:"summary" validate:"required"` + IsRead bool `json:"is_read"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} diff --git a/internal/domain/user/user.go b/internal/domain/user/user.go new file mode 100644 index 0000000..83f6af4 --- /dev/null +++ b/internal/domain/user/user.go @@ -0,0 +1,11 @@ +package user + +import "time" + +// User struct는 데이터베이스의 user 테이블과 매핑되는 엔티티입니다. +type User struct { + ID int64 `json:"id"` + Name string `json:"name" validate:"required,max=30"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} From ca4bb9eb9a54b591270bdeef4b4b81a973c655c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=80=ED=98=84?= <102128060+wlgusqkr@users.noreply.github.com> Date: Thu, 18 Sep 2025 11:45:52 +0900 Subject: [PATCH 3/4] chore: organize folder structure --- internal/domain/{order => subscription}/errors.go | 2 +- internal/domain/subscription/subscription.go | 14 ++++++++++++++ .../domain/subscription/subscriptionHandler.go | 1 + .../domain/subscription/subscriptionUsecase.go | 1 + internal/domain/{product => summary}/errors.go | 2 +- internal/domain/summary/summaryHandler.go | 1 + internal/domain/summary/summaryUsecase.go | 1 + internal/domain/user/userHandler.go | 1 + internal/domain/user/userUsecase.go | 1 + 9 files changed, 22 insertions(+), 2 deletions(-) rename internal/domain/{order => subscription}/errors.go (98%) create mode 100644 internal/domain/subscription/subscription.go create mode 100644 internal/domain/subscription/subscriptionHandler.go create mode 100644 internal/domain/subscription/subscriptionUsecase.go rename internal/domain/{product => summary}/errors.go (98%) create mode 100644 internal/domain/summary/summaryHandler.go create mode 100644 internal/domain/summary/summaryUsecase.go create mode 100644 internal/domain/user/userHandler.go create mode 100644 internal/domain/user/userUsecase.go diff --git a/internal/domain/order/errors.go b/internal/domain/subscription/errors.go similarity index 98% rename from internal/domain/order/errors.go rename to internal/domain/subscription/errors.go index e00ee7c..bc6fff9 100644 --- a/internal/domain/order/errors.go +++ b/internal/domain/subscription/errors.go @@ -1,4 +1,4 @@ -package order +package subscription import ( "net/http" diff --git a/internal/domain/subscription/subscription.go b/internal/domain/subscription/subscription.go new file mode 100644 index 0000000..dd1ca21 --- /dev/null +++ b/internal/domain/subscription/subscription.go @@ -0,0 +1,14 @@ +package subscription + +import "time" + +type Subscription struct { + ID int64 `json:"id"` + UserID int64 `json:"user_id" validate:"required,gt=0"` + URL string `json:"url" validate:"required,url"` + Alias string `json:"alias,omitempty" validate:"omitempty,max=30"` + KeywordFilter string `json:"keyword_filter,omitempty" validate:"omitempty,max=100"` + IsUrgent bool `json:"is_urgent"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} diff --git a/internal/domain/subscription/subscriptionHandler.go b/internal/domain/subscription/subscriptionHandler.go new file mode 100644 index 0000000..0e1dae2 --- /dev/null +++ b/internal/domain/subscription/subscriptionHandler.go @@ -0,0 +1 @@ +package subscription diff --git a/internal/domain/subscription/subscriptionUsecase.go b/internal/domain/subscription/subscriptionUsecase.go new file mode 100644 index 0000000..0e1dae2 --- /dev/null +++ b/internal/domain/subscription/subscriptionUsecase.go @@ -0,0 +1 @@ +package subscription diff --git a/internal/domain/product/errors.go b/internal/domain/summary/errors.go similarity index 98% rename from internal/domain/product/errors.go rename to internal/domain/summary/errors.go index c94cd09..dcb598f 100644 --- a/internal/domain/product/errors.go +++ b/internal/domain/summary/errors.go @@ -1,4 +1,4 @@ -package product +package summary import ( "net/http" diff --git a/internal/domain/summary/summaryHandler.go b/internal/domain/summary/summaryHandler.go new file mode 100644 index 0000000..74a9de2 --- /dev/null +++ b/internal/domain/summary/summaryHandler.go @@ -0,0 +1 @@ +package summary diff --git a/internal/domain/summary/summaryUsecase.go b/internal/domain/summary/summaryUsecase.go new file mode 100644 index 0000000..74a9de2 --- /dev/null +++ b/internal/domain/summary/summaryUsecase.go @@ -0,0 +1 @@ +package summary diff --git a/internal/domain/user/userHandler.go b/internal/domain/user/userHandler.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/internal/domain/user/userHandler.go @@ -0,0 +1 @@ +package user diff --git a/internal/domain/user/userUsecase.go b/internal/domain/user/userUsecase.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/internal/domain/user/userUsecase.go @@ -0,0 +1 @@ +package user From 875348fb9bae19e73d671f2784dc535d0936c02f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=A7=80=ED=98=84?= <102128060+wlgusqkr@users.noreply.github.com> Date: Thu, 18 Sep 2025 11:47:49 +0900 Subject: [PATCH 4/4] refactor: Migrate database from PostgreSQL to MySQL --- config/config.dev.yaml | 8 ++++---- go.mod | 2 ++ go.sum | 4 ++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/config/config.dev.yaml b/config/config.dev.yaml index 41072a9..91aa9ad 100644 --- a/config/config.dev.yaml +++ b/config/config.dev.yaml @@ -6,10 +6,10 @@ app: db: host: "localhost" - port: 5432 - user: "postgres" - password: "postgresadmin" - dbname: "sound_postgres" + port: 3305 + user: "root" + password: "root" + dbname: "main_db" sslmode: "disable" secure: diff --git a/go.mod b/go.mod index aaeeee6..92361df 100644 --- a/go.mod +++ b/go.mod @@ -13,10 +13,12 @@ require ( ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/go.sum b/go.sum index 6d654f2..5decfa9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -15,6 +17,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= 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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=