This commit is contained in:
Rzy 2026-04-13 14:23:47 +08:00
parent ef2d3f68b9
commit 0bb2e77016
16 changed files with 229 additions and 95 deletions

View File

@ -28,10 +28,14 @@ func InitializeApp(configConfig *config.Config, allLogger log.AllLogger) (*serve
publishImpl := impl.NewPublishImpl(db) publishImpl := impl.NewPublishImpl(db)
loginRelationImpl := impl.NewLoginRelationImpl(db) loginRelationImpl := impl.NewLoginRelationImpl(db)
publishBiz := biz.NewPublishBiz(configConfig, publishImpl, userImpl, platImpl, tokenImpl, loginRelationImpl) publishBiz := biz.NewPublishBiz(configConfig, publishImpl, userImpl, platImpl, tokenImpl, loginRelationImpl)
appService := service.NewAppService(configConfig, tokenImpl, userImpl, platImpl, publishBiz, loginRelationImpl) authBiz := biz.NewAuthBiz(configConfig, tokenImpl, userImpl)
loginService := service.NewLoginService(configConfig, publishBiz) appService := service.NewAppService(configConfig, tokenImpl, userImpl, platImpl, publishBiz, authBiz, loginRelationImpl)
publishService := service.NewPublishService(configConfig, publishBiz, db) loginService := service.NewLoginService(configConfig, publishBiz, authBiz)
appModule := router.NewAppModule(configConfig, appService, loginService, publishService) publishService := service.NewPublishService(configConfig, publishBiz, authBiz, db)
productImpl := impl.NewProductImpl(db)
productService := service.NewProductService(configConfig, productImpl, authBiz)
productSourceService := service.NewProductSourceService(configConfig, productImpl, authBiz)
appModule := router.NewAppModule(configConfig, appService, loginService, publishService, productService, productSourceService)
routerServer := router.NewRouterServer(appModule) routerServer := router.NewRouterServer(appModule)
app := server.NewHTTPServer(routerServer) app := server.NewHTTPServer(routerServer)
servers := server.NewServers(configConfig, app) servers := server.NewServers(configConfig, app)

View File

@ -6,6 +6,7 @@ import (
"geo/internal/data/impl" "geo/internal/data/impl"
"geo/internal/data/model" "geo/internal/data/model"
"geo/tmpl/errcode" "geo/tmpl/errcode"
"xorm.io/builder" "xorm.io/builder"
) )
@ -62,7 +63,7 @@ func (a *AuthBiz) UserValid(ctx context.Context, userIndex string, tokenId int32
return userInfo, nil return userInfo, nil
} }
func (a *AuthBiz) UserAndTokenValid(ctx context.Context, accessToken, userIndex string) (*model.User, *model.Token, error) { func (a *AuthBiz) UserAndTokenValid(ctx context.Context, userIndex, accessToken string) (*model.User, *model.Token, error) {
tokenInfo, err := a.ValidateAccessToken(ctx, accessToken) tokenInfo, err := a.ValidateAccessToken(ctx, accessToken)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err

View File

@ -0,0 +1,27 @@
package impl
import (
"geo/internal/data/model"
"geo/tmpl/dataTemp"
"geo/utils"
)
type ArticleTypeImpl struct {
dataTemp.DataTemp
db *utils.Db
}
func NewArticleTypeImpl(db *utils.Db) *ArticleTypeImpl {
return &ArticleTypeImpl{
DataTemp: *dataTemp.NewDataTemp(db, new(model.ArticleType)),
db: db,
}
}
func (m *ArticleTypeImpl) PrimaryKey() string {
return "id"
}
func (m *ArticleTypeImpl) GetTemp() *dataTemp.DataTemp {
return &m.DataTemp
}

View File

@ -11,8 +11,8 @@ type ProductImpl struct {
db *utils.Db db *utils.Db
} }
func NewProductImpl(db *utils.Db) *UserImpl { func NewProductImpl(db *utils.Db) *ProductImpl {
return &UserImpl{ return &ProductImpl{
DataTemp: *dataTemp.NewDataTemp(db, new(model.Product)), DataTemp: *dataTemp.NewDataTemp(db, new(model.Product)),
db: db, db: db,
} }

View File

@ -0,0 +1,27 @@
package impl
import (
"geo/internal/data/model"
"geo/tmpl/dataTemp"
"geo/utils"
)
type ProductSourceImpl struct {
dataTemp.DataTemp
db *utils.Db
}
func NewProductSourceImpl(db *utils.Db) *ProductSourceImpl {
return &ProductSourceImpl{
DataTemp: *dataTemp.NewDataTemp(db, new(model.ProductSource)),
db: db,
}
}
func (m *ProductSourceImpl) PrimaryKey() string {
return "id"
}
func (m *ProductSourceImpl) GetTemp() *dataTemp.DataTemp {
return &m.DataTemp
}

View File

@ -11,4 +11,6 @@ var ProviderImpl = wire.NewSet(
NewTokenImpl, NewTokenImpl,
NewPublishImpl, NewPublishImpl,
NewProductImpl, NewProductImpl,
NewProductSourceImpl,
NewArticleTypeImpl,
) )

View File

@ -0,0 +1,20 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
const TableNameArticalType = "article_type"
// ArticalType mapped from table <artical_type>
type ArticleType struct {
ID int32 `gorm:"column:id;primaryKey" json:"id"`
ArticleName string `gorm:"column:artical_name;not null" json:"artical_name"`
Desc string `gorm:"column:desc;not null" json:"desc"`
Status int32 `gorm:"column:status;not null;default:1" json:"status"`
}
// TableName ArticalType's table name
func (*ArticleType) TableName() string {
return TableNameArticalType
}

View File

@ -0,0 +1,41 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
"gorm.io/gorm"
)
const TableNameProduct = "product"
// Product mapped from table <product>
type Product struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
UserIndex string `gorm:"column:user_index;not null" json:"user_index"`
Name string `gorm:"column:name;not null" json:"name"`
Industry string `gorm:"column:industry;not null;comment:所属行业" json:"industry"` // 所属行业
Type string `gorm:"column:type;not null;comment:产品类型" json:"type"` // 产品类型
ProductOrService string `gorm:"column:product_or_service;comment:主营业务" json:"product_or_service"` // 主营业务
Advantages string `gorm:"column:advantages;comment:核心优势" json:"advantages"` // 核心优势
Story string `gorm:"column:story;comment:发展故事" json:"story"` // 发展故事
Problem string `gorm:"column:problem;comment:解决痛点" json:"problem"` // 解决痛点
Background string `gorm:"column:background;comment:信任背书" json:"background"` // 信任背书
Case string `gorm:"column:case;comment:品牌案例" json:"case"` // 品牌案例
Other string `gorm:"column:other;comment:其他信息" json:"other"` // 其他信息
ServiceCope string `gorm:"column:service_cope;comment:服务范围" json:"service_cope"` // 服务范围
TargetAudience string `gorm:"column:target_audience;comment:目标客户群体" json:"target_audience"` // 目标客户群体
Imgs string `gorm:"column:imgs;comment:图片" json:"imgs"` // 图片
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"`
Status int32 `gorm:"column:status;default:1" json:"status"`
}
// TableName Product's table name
func (*Product) TableName() string {
return TableNameProduct
}

View File

@ -1,36 +0,0 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"gorm.io/gorm"
"time"
)
const TableNameProduct = "product"
type Product struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
UserIndex string `gorm:"column:user_index;not null" json:"user_index"`
Name string `gorm:"column:name;not null;default:''" json:"name"`
Industry string `gorm:"column:industry;not null" json:"industry"`
Type string `gorm:"column:type;not null;default:''" json:"type"`
Advantages string `gorm:"column:advantages;type:text" json:"advantages"`
Problem string `gorm:"column:problem;type:text" json:"problem"`
Background string `gorm:"column:background;type:text" json:"background"`
Case string `gorm:"column:case;type:text" json:"case"`
Other string `gorm:"column:other;type:text" json:"other"`
ServiceCope string `gorm:"column:service_cope;size:100" json:"service_cope"`
TargetAudience string `gorm:"column:target_audience;size:255" json:"target_audience"`
CreateAt *time.Time `gorm:"column:create_at" json:"create_at"`
UpdateAt *time.Time `gorm:"column:update_at" json:"update_at"`
DeleteAt gorm.DeletedAt `gorm:"column:delete_at;index" json:"delete_at"`
Status int8 `gorm:"column:status;default:1" json:"status"`
}
// TableName LoginRelation's table name
func (*Product) TableName() string {
return TableNameProduct
}

View File

@ -0,0 +1,28 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameProductSource = "product_source"
// ProductSource mapped from table <product_source>
type ProductSource struct {
ProductSource int64 `gorm:"column:product_source;primaryKey" json:"product_source"`
Title string `gorm:"column:title;not null" json:"title"`
SourceURL string `gorm:"column:source_url;not null" json:"source_url"`
CreateAt time.Time `gorm:"column:create_at" json:"create_at"`
RecommendPlatfrom string `gorm:"column:recommend_platfrom" json:"recommend_platfrom"`
UpdateAt time.Time `gorm:"column:update_at" json:"update_at"`
DeleteAt time.Time `gorm:"column:delete_at" json:"delete_at"`
Status int32 `gorm:"column:status;default:1" json:"status"`
}
// TableName ProductSource's table name
func (*ProductSource) TableName() string {
return TableNameProductSource
}

View File

@ -88,19 +88,20 @@ type (
} }
CreateProductRequest struct { CreateProductRequest struct {
AccessToken string `json:"access_token" validate:"required" zh:"access_token"` AccessToken string `json:"access_token" validate:"required" zh:"access_token"`
UserIndex string `json:"user_index" validate:"required" zh:"用户索引"` UserIndex string `json:"user_index" validate:"required" zh:"用户索引"`
Name string `json:"name" validate:"required" zh:"产品名称"` Name string `json:"name" validate:"required" zh:"产品名称"`
Industry string `json:"industry" validate:"required" zh:"所属行业"` Industry string `json:"industry" validate:"required" zh:"所属行业"`
Type string `json:"type" validate:"required" zh:"产品类型"` Type string `json:"type" validate:"required" zh:"产品类型"`
Advantages string `json:"advantages" zh:"核心优势"` ProductOrService string `json:"product_or_service" validate:"required" zh:"主营业务"`
Story string `json:"story" zh:"发展故事"` Advantages string `json:"advantages" zh:"核心优势"`
Problem string `json:"problem" zh:"解决痛点"` Story string `json:"story" zh:"发展故事"`
Background string `json:"background" zh:"信任背书"` Problem string `json:"problem" zh:"解决痛点"`
Case string `json:"case" zh:"品牌案例"` Background string `json:"background" zh:"信任背书"`
Other string `json:"other" zh:"其他信息"` Case string `json:"case" zh:"品牌案例"`
ServiceCope string `json:"service_cope" zh:"服务范围"` Other string `json:"other" zh:"其他信息"`
TargetAudience string `json:"target_audience" zh:"目标客户群体"` ServiceCope string `json:"service_cope" zh:"服务范围"`
TargetAudience string `json:"target_audience" zh:"目标客户群体"`
} }
ProductListRequest struct { ProductListRequest struct {
@ -111,15 +112,25 @@ type (
} }
ProductUpdateRequest struct { ProductUpdateRequest struct {
AccessToken string `json:"access_token" validate:"required" zh:"access_token"` AccessToken string `json:"access_token" validate:"required" zh:"access_token"`
UserIndex string `json:"user_index" validate:"required" zh:"用户索引"` Id int32 `json:"id" validate:"required" zh:"id"`
PlatIndex string `json:"plat_index" validate:"required" zh:"平台索引"` Name string `json:"name" zh:"产品名称"`
Industry string `json:"industry" zh:"所属行业"`
ProductOrService string `json:"product_or_service" zh:"主营业务"`
Type string `json:"type" zh:"产品类型"`
Advantages string `json:"advantages" zh:"核心优势"`
Story string `json:"story" zh:"发展故事"`
Problem string `json:"problem" zh:"解决痛点"`
Background string `json:"background" zh:"信任背书"`
Case string `json:"case" zh:"品牌案例"`
Other string `json:"other" zh:"其他信息"`
ServiceCope string `json:"service_cope" zh:"服务范围"`
TargetAudience string `json:"target_audience" zh:"目标客户群体"`
} }
ProductDelRequest struct { ProductDelRequest struct {
AccessToken string `json:"access_token" validate:"required" zh:"access_token"` AccessToken string `json:"access_token" validate:"required" zh:"access_token"`
UserIndex string `json:"user_index" validate:"required" zh:"用户索引"` Id int32 `json:"id" validate:"required" zh:"产品id"`
PlatIndex string `json:"plat_index" validate:"required" zh:"平台索引"`
} }
ProductSourceCreateRequest struct { ProductSourceCreateRequest struct {

View File

@ -4,6 +4,7 @@ import (
"geo/internal/config" "geo/internal/config"
"geo/internal/entitys" "geo/internal/entitys"
"geo/internal/service" "geo/internal/service"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@ -50,13 +51,13 @@ func (m *AppModule) Register(router fiber.Router) {
router.Post("/logout_platform", vali(m.loginService.LogoutPlatform, &entitys.LogoutPlatformRequest{})) router.Post("/logout_platform", vali(m.loginService.LogoutPlatform, &entitys.LogoutPlatformRequest{}))
router.Get("/logs/:request_id", m.loginService.Log) router.Get("/logs/:request_id", m.loginService.Log)
router.Get("/product/add", vali(m.productService.Add, &entitys.ProductAddRequest{})) router.Post("/product/add", vali(m.productService.Add, &entitys.CreateProductRequest{}))
router.Get("/product/list", vali(m.productService.List, &entitys.ProductListRequest{})) router.Post("/product/list", vali(m.productService.List, &entitys.ProductListRequest{}))
router.Get("/product/update", vali(m.productService.Update, &entitys.ProductUpdateRequest{})) router.Post("/product/update", vali(m.productService.Update, &entitys.ProductUpdateRequest{}))
router.Get("/product/del", vali(m.productService.Del, &entitys.ProductDelRequest{})) router.Post("/product/del", vali(m.productService.Del, &entitys.ProductDelRequest{}))
router.Get("/product/word/create", vali(m.productSourceService.Create, &entitys.ProductSourceCreateRequest{})) router.Post("/product/word/create", vali(m.productSourceService.Create, &entitys.ProductSourceCreateRequest{}))
router.Get("/product/word/list", vali(m.productSourceService.List, &entitys.ProductSourceListRequest{})) router.Post("/product/word/list", vali(m.productSourceService.List, &entitys.ProductSourceListRequest{}))
router.Get("/product/word/upload", vali(m.productSourceService.Upload, &entitys.ProductSourceUploadRequest{})) router.Post("/product/word/upload", vali(m.productSourceService.Upload, &entitys.ProductSourceUploadRequest{}))
router.Get("/product/word/del", vali(m.productSourceService.Del, &entitys.ProductSourceDelRequest{})) router.Post("/product/word/del", vali(m.productSourceService.Del, &entitys.ProductSourceDelRequest{}))
} }

View File

@ -8,7 +8,10 @@ import (
"geo/internal/entitys" "geo/internal/entitys"
"geo/pkg" "geo/pkg"
"geo/tmpl/dataTemp" "geo/tmpl/dataTemp"
"github.com/go-viper/mapstructure/v2"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"xorm.io/builder"
) )
type ProductService struct { type ProductService struct {
@ -26,24 +29,16 @@ func NewProductService(cfg *config.Config, ProductImpl *impl.ProductImpl, authBi
} }
func (p *ProductService) Add(c *fiber.Ctx, req *entitys.CreateProductRequest) error { func (p *ProductService) Add(c *fiber.Ctx, req *entitys.CreateProductRequest) error {
userInfo, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken) _, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken)
if err != nil { if err != nil {
return err return err
} }
add := &model.Product{ var data model.Product
UserIndex: userInfo.UserIndex, err = mapstructure.Decode(req, &data)
Name: req.Name, if err != nil {
Industry: req.Industry, return err
Type: req.Type,
Advantages: req.Advantages,
Problem: req.Problem,
Background: req.Background,
Case: req.Case,
Other: req.Other,
ServiceCope: req.ServiceCope,
TargetAudience: req.TargetAudience,
} }
err = p.productImpl.Add(c.UserContext(), add) err = p.productImpl.Add(c.UserContext(), &data)
if err != nil { if err != nil {
return err return err
} }
@ -67,7 +62,10 @@ func (p *ProductService) List(c *fiber.Ctx, req *entitys.ProductListRequest) err
pageSize = 100 pageSize = 100
} }
var list []*model.Product var list []*model.Product
total, err := p.productImpl.GetListToStruct(c.UserContext(), nil, &dataTemp.ReqPageBo{Page: page, Limit: pageSize}, list, "") cond := builder.NewCond().
And(builder.Eq{"user_index": req.UserIndex}).
And(builder.Eq{"status": 1})
total, err := p.productImpl.GetListToStruct(c.UserContext(), &cond, &dataTemp.ReqPageBo{Page: page, Limit: pageSize}, &list, "")
if err != nil { if err != nil {
return err return err
} }
@ -76,17 +74,24 @@ func (p *ProductService) List(c *fiber.Ctx, req *entitys.ProductListRequest) err
} }
func (p *ProductService) Update(c *fiber.Ctx, req *entitys.ProductUpdateRequest) error { func (p *ProductService) Update(c *fiber.Ctx, req *entitys.ProductUpdateRequest) error {
userInfo, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken) _, err := p.authBiz.ValidateAccessToken(c.UserContext(), req.AccessToken)
if err != nil { if err != nil {
return err return err
} }
var data model.Product
err = mapstructure.Decode(req, &data)
if err != nil {
return err
}
err = p.productImpl.UpdateByKey(c.UserContext(), p.productImpl.PrimaryKey(), req.Id, &data)
return nil return nil
} }
func (p *ProductService) Del(c *fiber.Ctx, req *entitys.ProductDelRequest) error { func (p *ProductService) Del(c *fiber.Ctx, req *entitys.ProductDelRequest) error {
userInfo, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken) _, err := p.authBiz.ValidateAccessToken(c.UserContext(), req.AccessToken)
if err != nil { if err != nil {
return err return err
} }
err = p.productImpl.DeleteByKey(c.UserContext(), p.productImpl.PrimaryKey(), req.Id)
return nil return nil
} }

View File

@ -5,6 +5,7 @@ import (
"geo/internal/config" "geo/internal/config"
"geo/internal/data/impl" "geo/internal/data/impl"
"geo/internal/entitys" "geo/internal/entitys"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@ -24,7 +25,7 @@ func NewProductSourceService(cfg *config.Config, ProductImpl *impl.ProductImpl,
func (p *ProductSourceService) Create(c *fiber.Ctx, req *entitys.ProductSourceCreateRequest) error { func (p *ProductSourceService) Create(c *fiber.Ctx, req *entitys.ProductSourceCreateRequest) error {
// 验证token // 验证token
userInfo, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken) _, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken)
if err != nil { if err != nil {
return err return err
} }
@ -34,7 +35,7 @@ func (p *ProductSourceService) Create(c *fiber.Ctx, req *entitys.ProductSourceCr
func (p *ProductSourceService) List(c *fiber.Ctx, req *entitys.ProductSourceListRequest) error { func (p *ProductSourceService) List(c *fiber.Ctx, req *entitys.ProductSourceListRequest) error {
// 验证token // 验证token
userInfo, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken) _, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken)
if err != nil { if err != nil {
return err return err
} }
@ -44,7 +45,7 @@ func (p *ProductSourceService) List(c *fiber.Ctx, req *entitys.ProductSourceList
func (p *ProductSourceService) Upload(c *fiber.Ctx, req *entitys.ProductSourceUploadRequest) error { func (p *ProductSourceService) Upload(c *fiber.Ctx, req *entitys.ProductSourceUploadRequest) error {
// 验证token // 验证token
userInfo, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken) _, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken)
if err != nil { if err != nil {
return err return err
} }
@ -54,7 +55,7 @@ func (p *ProductSourceService) Upload(c *fiber.Ctx, req *entitys.ProductSourceUp
func (p *ProductSourceService) Del(c *fiber.Ctx, req *entitys.ProductSourceDelRequest) error { func (p *ProductSourceService) Del(c *fiber.Ctx, req *entitys.ProductSourceDelRequest) error {
// 验证token // 验证token
userInfo, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken) _, _, err := p.authBiz.UserAndTokenValid(c.UserContext(), req.UserIndex, req.AccessToken)
if err != nil { if err != nil {
return err return err
} }

View File

@ -191,9 +191,11 @@ func (k DataTemp) GetListToStruct(ctx context.Context, cond *builder.Cond, pageB
if elem.Kind() != reflect.Slice { if elem.Kind() != reflect.Slice {
return nil, fmt.Errorf("result must be a pointer to slice") return nil, fmt.Errorf("result must be a pointer to slice")
} }
var query string
// 构建基础查询 // 构建基础查询
query, _ := builder.ToBoundSQL(*cond) if cond != nil {
query, _ = builder.ToBoundSQL(*cond)
}
// 预编译 SQL 以提高性能 // 预编译 SQL 以提高性能
// 使用 Table 指定表名,避免 GORM 的反射开销 // 使用 Table 指定表名,避免 GORM 的反射开销

View File

@ -4,10 +4,10 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"geo/internal/config" "geo/internal/config"
"time"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger"
"time"
) )
func DBConn(c *config.DB) (*gorm.DB, func()) { func DBConn(c *config.DB) (*gorm.DB, func()) {
@ -15,7 +15,7 @@ func DBConn(c *config.DB) (*gorm.DB, func()) {
gormDB, err := gorm.Open( gormDB, err := gorm.Open(
mysql.New(mysql.Config{Conn: mysqlConn}), mysql.New(mysql.Config{Conn: mysqlConn}),
) )
gormDB.Logger = gormDB.Logger.LogMode(logger.Silent) //gormDB.Logger = gormDB.Logger.LogMode(logger.Silent)
if err != nil { if err != nil {
panic("failed to connect database") panic("failed to connect database")
} }