From 915f12eaf7cde8f765c451363988f7acf6574968 Mon Sep 17 00:00:00 2001 From: ericprd Date: Sun, 2 Mar 2025 04:36:17 +0800 Subject: [PATCH] feat: news router --- .vscode/launch.json | 22 ++ cmd/gorm/main.go | 2 + database/category_model.go | 14 +- database/new_db.go | 20 +- database/news_model.go | 36 +++- database/tag_module.go | 10 +- database/user_model.go | 2 +- internal/accessor/category/create_model.go | 22 ++ internal/accessor/category/get_all_model.go | 15 ++ internal/accessor/category/get_bulk.go | 15 ++ internal/accessor/category/get_bulk_model.go | 15 ++ internal/accessor/category/impl.go | 6 + internal/accessor/module.go | 2 + internal/accessor/news/create.go | 13 ++ internal/accessor/news/create_model.go | 13 ++ internal/accessor/news/get_all.go | 13 ++ internal/accessor/news/get_all_model.go | 13 ++ internal/accessor/news/impl.go | 21 ++ internal/accessor/tag/create_model.go | 22 ++ internal/accessor/tag/get_all_model.go | 13 ++ internal/accessor/tag/get_bulk.go | 13 ++ internal/accessor/tag/get_bulk_model.go | 13 ++ internal/accessor/tag/impl.go | 4 + .../user_repository/get_user_by_email.go | 2 +- internal/api/http/auth/profile.go | 84 +------- internal/api/http/category/get_all.go | 3 +- internal/api/http/news/create.go | 93 +++++++++ internal/api/http/news/get_all.go | 32 +++ internal/api/http/news/module.go | 8 + internal/api/http/router.go | 2 + internal/api/http/tag/get_all.go | 5 +- internal/domain/category/spec.go | 2 +- internal/domain/news/request.go | 12 -- internal/domain/news/spec.go | 32 +++ internal/domain/tag/spec.go | 2 +- internal/services/category/create.go | 3 +- internal/services/category/get_all_model.go | 9 + internal/services/category/impl.go | 2 + internal/services/module.go | 2 + internal/services/news/create.go | 47 +++++ internal/services/news/create_model.go | 48 +++++ internal/services/news/get_all.go | 7 + internal/services/news/get_all_model.go | 9 + internal/services/news/impl.go | 34 ++++ internal/services/tag/create.go | 3 +- internal/services/tag/get_all_model.go | 9 + internal/services/tag/impl.go | 2 + internal/utilities/utils/get_token_detail.go | 34 ++++ openapi.yml | 190 ++++++++++++++++-- 49 files changed, 863 insertions(+), 132 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 internal/accessor/category/create_model.go create mode 100644 internal/accessor/category/get_all_model.go create mode 100644 internal/accessor/category/get_bulk.go create mode 100644 internal/accessor/category/get_bulk_model.go create mode 100644 internal/accessor/news/create.go create mode 100644 internal/accessor/news/create_model.go create mode 100644 internal/accessor/news/get_all.go create mode 100644 internal/accessor/news/get_all_model.go create mode 100644 internal/accessor/news/impl.go create mode 100644 internal/accessor/tag/create_model.go create mode 100644 internal/accessor/tag/get_all_model.go create mode 100644 internal/accessor/tag/get_bulk.go create mode 100644 internal/accessor/tag/get_bulk_model.go create mode 100644 internal/api/http/news/create.go create mode 100644 internal/api/http/news/get_all.go create mode 100644 internal/api/http/news/module.go delete mode 100644 internal/domain/news/request.go create mode 100644 internal/domain/news/spec.go create mode 100644 internal/services/category/get_all_model.go create mode 100644 internal/services/news/create.go create mode 100644 internal/services/news/create_model.go create mode 100644 internal/services/news/get_all.go create mode 100644 internal/services/news/get_all_model.go create mode 100644 internal/services/news/impl.go create mode 100644 internal/services/tag/get_all_model.go create mode 100644 internal/utilities/utils/get_token_detail.go diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..51ecc3f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}/cmd/legalgo/", + "dlvLoadConfig":{ + "followPointers": true, + "maxVariableRecurse": 1, + "maxStringLen": 512, // 字符串最大长度 + "maxArrayValues": 64, + "maxStructFields": -1 + }, + "dlvFlags": ["--check-go-version=false"], + "env": { + "TZ": "" + } + }] + } \ No newline at end of file diff --git a/cmd/gorm/main.go b/cmd/gorm/main.go index 5a7011a..f925859 100644 --- a/cmd/gorm/main.go +++ b/cmd/gorm/main.go @@ -29,6 +29,8 @@ func main() { log.Fatalf("failed to connect to database: %v", err) } + // db.DropTables() + if err := db.Migrate(); err != nil { log.Fatal("Migration failed: ", err) } diff --git a/database/category_model.go b/database/category_model.go index ea687e0..8f43829 100644 --- a/database/category_model.go +++ b/database/category_model.go @@ -1,11 +1,21 @@ package database -import "time" +import ( + "time" +) type Category struct { - ID string `gorm:"primaryKey;not null" json:"id"` + ID string `gorm:"primaryKey" json:"id"` Code string `gorm:"not null;unique" json:"code"` Name string `gorm:"not null" json:"name"` CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"` } + +type CategoryModel struct { + ID string `gorm:"primaryKey" json:"id"` + Name string `gorm:"not null;unique" json:"name"` + Code string `gorm:"not null" json:"code"` + CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` + UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"` +} diff --git a/database/new_db.go b/database/new_db.go index a539202..be0491d 100644 --- a/database/new_db.go +++ b/database/new_db.go @@ -36,6 +36,18 @@ func NewDB() (*DB, error) { return &DB{db}, nil } +func (db *DB) DropTables() error { + // Auto Migrate the User model + return db.Migrator().DropTable( + // &Staff{}, + // &SubscribePlan{}, + // &Subscribe{}, + // &User{}, + &Tag{}, + &Category{}, + &News{}, + ) +} func (db *DB) Migrate() error { // Auto Migrate the User model return db.AutoMigrate( @@ -43,7 +55,11 @@ func (db *DB) Migrate() error { &SubscribePlan{}, &Subscribe{}, &User{}, - &Tag{}, - &Category{}, + // &Tag{}, + // &Category{}, + // &News{}, + &NewsModel{}, + &TagModel{}, + &CategoryModel{}, ) } diff --git a/database/news_model.go b/database/news_model.go index 77d6fab..fedff08 100644 --- a/database/news_model.go +++ b/database/news_model.go @@ -5,13 +5,31 @@ import ( ) type News struct { - ID string `gorm:"primaryKey" json:"id"` - AuthorID string `gorm:"not null" json:"author_id"` - Title string `gorm:"default:null" json:"title"` - Content string `gorm:"default:null" json:"content"` - IsPremium bool `gorm:"default:false" json:"is_premium"` - Slug string `gorm:"default:null" json:"slug"` - FeaturedImage string `gorm:"default:null" json:"featured_image"` - CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` - UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"` + ID string `gorm:"primaryKey" json:"id"` + Title string `gorm:"default:null" json:"title"` + Tags []Tag `gorm:"many2many:news_tags" json:"tags"` + Categories []Category `gorm:"many2many:news_categories" json:"categories"` + Content string `gorm:"default:null" json:"content"` + LiveAt time.Time `gorm:"not null" json:"live_at"` + AuthorID string `gorm:"not null" json:"author_id"` + IsPremium bool `gorm:"default:false" json:"is_premium"` + Slug string `gorm:"default:null" json:"slug"` + FeaturedImage string `gorm:"default:null" json:"featured_image"` + CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` + UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"` +} + +type NewsModel struct { + ID string `gorm:"primaryKey" json:"id"` + Title string `json:"title"` + Content string `json:"content"` + Categories []CategoryModel `gorm:"many2many:news_categories" json:"categories"` + Tags []TagModel `gorm:"many2many:news_tags" json:"tags"` + IsPremium bool `gorm:"default:false" json:"is_premium"` + Slug string `gorm:"default:null" json:"slug"` + FeaturedImage string `gorm:"default:null" json:"featured_image"` + AuthorID string `gorm:"not null" json:"author_id"` + LiveAt time.Time `gorm:"not null" json:"live_at"` + CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` + UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"` } diff --git a/database/tag_module.go b/database/tag_module.go index ce2271f..ae3aec0 100644 --- a/database/tag_module.go +++ b/database/tag_module.go @@ -5,9 +5,17 @@ import ( ) type Tag struct { - ID string `gorm:"primaryKey;not null" json:"id"` + ID string `gorm:"primaryKey" json:"id"` Code string `gorm:"not null;unique" json:"code"` Name string `gorm:"not null" json:"name"` CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"` } + +type TagModel struct { + ID string `gorm:"primaryKey" json:"id"` + Name string `gorm:"not null;unique" json:"name"` + Code string `gorm:"not null" json:"code"` + CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` + UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"` +} diff --git a/database/user_model.go b/database/user_model.go index 263b5bc..6ad2454 100644 --- a/database/user_model.go +++ b/database/user_model.go @@ -5,7 +5,7 @@ import ( ) type User struct { - ID string `gorm:"primaryKey" json:"id"` + ID string `gorm:"primaryKey;not null" json:"id"` SubscribeID string `gorm:"not null" json:"subscribe_id"` Email string `gorm:"unique;not null" json:"email"` Password string `gorm:"not null" json:"password"` diff --git a/internal/accessor/category/create_model.go b/internal/accessor/category/create_model.go new file mode 100644 index 0000000..f6ce13f --- /dev/null +++ b/internal/accessor/category/create_model.go @@ -0,0 +1,22 @@ +package categoryrepository + +import ( + "legalgo-BE-go/database" + categorydomain "legalgo-BE-go/internal/domain/category" + + "github.com/google/uuid" +) + +func (a *accessor) CreateModel(spec categorydomain.CategoryReq) error { + data := database.CategoryModel{ + ID: uuid.NewString(), + Name: spec.Name, + Code: spec.Code, + } + + if err := a.DB.Create(&data).Error; err != nil { + return err + } + + return nil +} diff --git a/internal/accessor/category/get_all_model.go b/internal/accessor/category/get_all_model.go new file mode 100644 index 0000000..ac7b681 --- /dev/null +++ b/internal/accessor/category/get_all_model.go @@ -0,0 +1,15 @@ +package categoryrepository + +import ( + "legalgo-BE-go/database" +) + +func (a *accessor) GetAllModel() ([]database.CategoryModel, error) { + var categories []database.CategoryModel + + if err := a.DB.Find(&categories).Error; err != nil { + return nil, err + } + + return categories, nil +} diff --git a/internal/accessor/category/get_bulk.go b/internal/accessor/category/get_bulk.go new file mode 100644 index 0000000..501ec95 --- /dev/null +++ b/internal/accessor/category/get_bulk.go @@ -0,0 +1,15 @@ +package categoryrepository + +import ( + categorydomain "legalgo-BE-go/internal/domain/category" +) + +func (a *accessor) GetByIDs(ids []string) ([]categorydomain.Category, error) { + var categories []categorydomain.Category + + if err := a.DB.Find(&categories, "id IN ?", ids).Error; err != nil { + return nil, err + } + + return categories, nil +} diff --git a/internal/accessor/category/get_bulk_model.go b/internal/accessor/category/get_bulk_model.go new file mode 100644 index 0000000..43477f6 --- /dev/null +++ b/internal/accessor/category/get_bulk_model.go @@ -0,0 +1,15 @@ +package categoryrepository + +import ( + "legalgo-BE-go/database" +) + +func (a *accessor) GetBulks(ids []string) ([]database.CategoryModel, error) { + var categories []database.CategoryModel + + if err := a.DB.Find(&categories, "id IN ?", ids).Error; err != nil { + return nil, err + } + + return categories, nil +} diff --git a/internal/accessor/category/impl.go b/internal/accessor/category/impl.go index 8171fe0..0b0ba62 100644 --- a/internal/accessor/category/impl.go +++ b/internal/accessor/category/impl.go @@ -11,7 +11,13 @@ type accessor struct { type Category interface { Create(categorydomain.CategoryReq) error + CreateModel(categorydomain.CategoryReq) error + GetAll() ([]categorydomain.Category, error) + GetAllModel() ([]database.CategoryModel, error) + + GetByIDs([]string) ([]categorydomain.Category, error) + GetBulks([]string) ([]database.CategoryModel, error) } func New( diff --git a/internal/accessor/module.go b/internal/accessor/module.go index 5e1d941..69fe54d 100644 --- a/internal/accessor/module.go +++ b/internal/accessor/module.go @@ -2,6 +2,7 @@ package repository import ( categoryrepository "legalgo-BE-go/internal/accessor/category" + newsrepository "legalgo-BE-go/internal/accessor/news" redisaccessor "legalgo-BE-go/internal/accessor/redis" staffrepository "legalgo-BE-go/internal/accessor/staff" subscriberepository "legalgo-BE-go/internal/accessor/subscribe" @@ -20,4 +21,5 @@ var Module = fx.Module("repository", fx.Provide( subscriberepository.New, tagrepository.New, categoryrepository.New, + newsrepository.New, )) diff --git a/internal/accessor/news/create.go b/internal/accessor/news/create.go new file mode 100644 index 0000000..5d33993 --- /dev/null +++ b/internal/accessor/news/create.go @@ -0,0 +1,13 @@ +package newsrepository + +import ( + "fmt" + newsdomain "legalgo-BE-go/internal/domain/news" +) + +func (a *accessor) Create(spec *newsdomain.News) error { + if err := a.db.Create(&spec).Error; err != nil { + return fmt.Errorf("failed to create news: %w", err) + } + return nil +} diff --git a/internal/accessor/news/create_model.go b/internal/accessor/news/create_model.go new file mode 100644 index 0000000..dbcf1e0 --- /dev/null +++ b/internal/accessor/news/create_model.go @@ -0,0 +1,13 @@ +package newsrepository + +import ( + "fmt" + "legalgo-BE-go/database" +) + +func (a *accessor) CreateModel(spec database.NewsModel) error { + if err := a.db.Create(&spec).Error; err != nil { + return fmt.Errorf("failed to create news: %w", err) + } + return nil +} diff --git a/internal/accessor/news/get_all.go b/internal/accessor/news/get_all.go new file mode 100644 index 0000000..ee82cec --- /dev/null +++ b/internal/accessor/news/get_all.go @@ -0,0 +1,13 @@ +package newsrepository + +import newsdomain "legalgo-BE-go/internal/domain/news" + +func (a *accessor) GetAll() ([]newsdomain.News, error) { + var news []newsdomain.News + + if err := a.db.Preload("Tags").Preload("Categories").Find(&news).Error; err != nil { + return nil, err + } + + return news, nil +} diff --git a/internal/accessor/news/get_all_model.go b/internal/accessor/news/get_all_model.go new file mode 100644 index 0000000..9cd8bf2 --- /dev/null +++ b/internal/accessor/news/get_all_model.go @@ -0,0 +1,13 @@ +package newsrepository + +import "legalgo-BE-go/database" + +func (a *accessor) GetAllModel() ([]database.NewsModel, error) { + var news []database.NewsModel + + if err := a.db.Preload("Tags").Preload("Categories").Find(&news).Error; err != nil { + return nil, err + } + + return news, nil +} diff --git a/internal/accessor/news/impl.go b/internal/accessor/news/impl.go new file mode 100644 index 0000000..11abb54 --- /dev/null +++ b/internal/accessor/news/impl.go @@ -0,0 +1,21 @@ +package newsrepository + +import ( + "legalgo-BE-go/database" + newsdomain "legalgo-BE-go/internal/domain/news" +) + +type accessor struct { + db *database.DB +} + +type News interface { + GetAll() ([]newsdomain.News, error) + GetAllModel() ([]database.NewsModel, error) + Create(*newsdomain.News) error + CreateModel(database.NewsModel) error +} + +func New(db *database.DB) News { + return &accessor{db} +} diff --git a/internal/accessor/tag/create_model.go b/internal/accessor/tag/create_model.go new file mode 100644 index 0000000..ce6e54c --- /dev/null +++ b/internal/accessor/tag/create_model.go @@ -0,0 +1,22 @@ +package tagrepository + +import ( + "legalgo-BE-go/database" + tagdomain "legalgo-BE-go/internal/domain/tag" + + "github.com/google/uuid" +) + +func (acc *accessor) CreateModel(spec tagdomain.TagReq) error { + data := &database.TagModel{ + ID: uuid.NewString(), + Code: spec.Code, + Name: spec.Name, + } + + if err := acc.DB.Create(&data).Error; err != nil { + return err + } + + return nil +} diff --git a/internal/accessor/tag/get_all_model.go b/internal/accessor/tag/get_all_model.go new file mode 100644 index 0000000..3fe5ad3 --- /dev/null +++ b/internal/accessor/tag/get_all_model.go @@ -0,0 +1,13 @@ +package tagrepository + +import "legalgo-BE-go/database" + +func (acc *accessor) GetAllModel() ([]database.TagModel, error) { + var tags []database.TagModel + + if err := acc.DB.Find(&tags).Error; err != nil { + return nil, err + } + + return tags, nil +} diff --git a/internal/accessor/tag/get_bulk.go b/internal/accessor/tag/get_bulk.go new file mode 100644 index 0000000..e2f483b --- /dev/null +++ b/internal/accessor/tag/get_bulk.go @@ -0,0 +1,13 @@ +package tagrepository + +import tagdomain "legalgo-BE-go/internal/domain/tag" + +func (a *accessor) GetByIDs(ids []string) ([]tagdomain.Tag, error) { + var tags []tagdomain.Tag + + if err := a.DB.Find(&tags, "id IN ?", ids).Error; err != nil { + return nil, err + } + + return tags, nil +} diff --git a/internal/accessor/tag/get_bulk_model.go b/internal/accessor/tag/get_bulk_model.go new file mode 100644 index 0000000..0072eb1 --- /dev/null +++ b/internal/accessor/tag/get_bulk_model.go @@ -0,0 +1,13 @@ +package tagrepository + +import "legalgo-BE-go/database" + +func (a *accessor) GetBulks(ids []string) ([]database.TagModel, error) { + var tags []database.TagModel + + if err := a.DB.Find(&tags, "id IN ?", ids).Error; err != nil { + return nil, err + } + + return tags, nil +} diff --git a/internal/accessor/tag/impl.go b/internal/accessor/tag/impl.go index 49987cb..0d00149 100644 --- a/internal/accessor/tag/impl.go +++ b/internal/accessor/tag/impl.go @@ -11,7 +11,11 @@ type accessor struct { type TagAccessor interface { Create(tagdomain.TagReq) error + CreateModel(tagdomain.TagReq) error GetAll() ([]tagdomain.Tag, error) + GetAllModel() ([]database.TagModel, error) + GetByIDs([]string) ([]tagdomain.Tag, error) + GetBulks(ids []string) ([]database.TagModel, error) } func New( diff --git a/internal/accessor/user_repository/get_user_by_email.go b/internal/accessor/user_repository/get_user_by_email.go index 2239cc5..37594d0 100644 --- a/internal/accessor/user_repository/get_user_by_email.go +++ b/internal/accessor/user_repository/get_user_by_email.go @@ -15,7 +15,7 @@ func (ur *UserRepository) GetUserByEmail(email string) (*authdomain.User, error) return nil, errors.New("email is empty") } - if err := ur.DB.First(&user, "email = ?", email).Error; err != nil { + if err := ur.DB.Find(&user, "email = ?", email).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("user not found") } diff --git a/internal/api/http/auth/profile.go b/internal/api/http/auth/profile.go index 663d2a1..bacea45 100644 --- a/internal/api/http/auth/profile.go +++ b/internal/api/http/auth/profile.go @@ -1,12 +1,10 @@ package authhttp import ( - "errors" authsvc "legalgo-BE-go/internal/services/auth" "legalgo-BE-go/internal/utilities/response" "legalgo-BE-go/internal/utilities/utils" "net/http" - "strings" "github.com/go-chi/chi/v5" ) @@ -17,46 +15,7 @@ func GetStaffProfile( ) { router.Get("/staff/profile", func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - authHeader := r.Header.Get("Authorization") - - if authHeader == "" { - response.ResponseWithErrorCode( - ctx, - w, - errors.New("provided auth is empty"), - response.ErrBadRequest.Code, - response.ErrBadRequest.HttpCode, - "required params is not provided", - ) - return - } - - if !strings.HasPrefix(authHeader, "Bearer") { - response.ResponseWithErrorCode( - ctx, - w, - errors.New("invalid authorization token"), - response.ErrBadRequest.Code, - response.ErrBadRequest.HttpCode, - "invalid required token", - ) - return - } - - token := strings.Split(authHeader, " ") - if len(token) < 2 { - response.ResponseWithErrorCode( - ctx, - w, - errors.New("invalid authorization"), - response.ErrBadRequest.Code, - response.ErrBadRequest.HttpCode, - "invalid required token", - ) - return - } - - destructedToken, err := utils.DestructToken(token[1]) + destructedToken, err := utils.GetTokenDetail(r) if err != nil { response.ResponseWithErrorCode( ctx, @@ -92,46 +51,7 @@ func GetUserProfile( ) { router.Get("/user/profile", func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - authHeader := r.Header.Get("Authorization") - - if authHeader == "" { - response.ResponseWithErrorCode( - ctx, - w, - errors.New("provided auth is empty"), - response.ErrBadRequest.Code, - response.ErrBadRequest.HttpCode, - "required params is not provided", - ) - return - } - - if !strings.HasPrefix(authHeader, "Bearer") { - response.ResponseWithErrorCode( - ctx, - w, - errors.New("invalid authorization token"), - response.ErrBadRequest.Code, - response.ErrBadRequest.HttpCode, - "invalid required token", - ) - return - } - - token := strings.Split(authHeader, " ") - if len(token) < 2 { - response.ResponseWithErrorCode( - ctx, - w, - errors.New("invalid authorization"), - response.ErrBadRequest.Code, - response.ErrBadRequest.HttpCode, - "invalid required token", - ) - return - } - - destructedToken, err := utils.DestructToken(token[1]) + destructedToken, err := utils.GetTokenDetail(r) if err != nil { response.ResponseWithErrorCode( ctx, diff --git a/internal/api/http/category/get_all.go b/internal/api/http/category/get_all.go index be13c90..935d385 100644 --- a/internal/api/http/category/get_all.go +++ b/internal/api/http/category/get_all.go @@ -14,7 +14,8 @@ func GetAll( ) { router.Get("/category", func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - subsPlan, err := categorySvc.GetAll() + subsPlan, err := categorySvc.GetAllModel() + // subsPlan, err := categorySvc.GetAll() if err != nil { response.ResponseWithErrorCode( ctx, diff --git a/internal/api/http/news/create.go b/internal/api/http/news/create.go new file mode 100644 index 0000000..affe248 --- /dev/null +++ b/internal/api/http/news/create.go @@ -0,0 +1,93 @@ +package newshttp + +import ( + staffrepository "legalgo-BE-go/internal/accessor/staff" + newsdomain "legalgo-BE-go/internal/domain/news" + newssvc "legalgo-BE-go/internal/services/news" + "legalgo-BE-go/internal/utilities/response" + "legalgo-BE-go/internal/utilities/utils" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-playground/validator/v10" +) + +func Create( + validate *validator.Validate, + newsSvc newssvc.News, + staffRepo staffrepository.StaffIntf, + router chi.Router, +) { + router.Post("/news/create", func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var spec newsdomain.NewsReq + + if err := utils.UnmarshalBody(r, &spec); err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + err.Error(), + ) + return + } + + if err := validate.Struct(spec); err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + err.(validator.ValidationErrors).Error(), + ) + return + } + + destructedToken, err := utils.GetTokenDetail(r) + if err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + err.Error(), + ) + return + } + + staffProfile, err := staffRepo.GetStaffByEmail(destructedToken.Email) + if err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + err.Error(), + ) + return + } + + if err := newsSvc.CreateModel(spec, staffProfile.ID); err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + err.Error(), + ) + return + } + + response.RespondJsonSuccess(ctx, w, struct { + Message string + }{ + Message: "news created successfully.", + }) + }) +} diff --git a/internal/api/http/news/get_all.go b/internal/api/http/news/get_all.go new file mode 100644 index 0000000..b9ff528 --- /dev/null +++ b/internal/api/http/news/get_all.go @@ -0,0 +1,32 @@ +package newshttp + +import ( + newssvc "legalgo-BE-go/internal/services/news" + "legalgo-BE-go/internal/utilities/response" + "net/http" + + "github.com/go-chi/chi/v5" +) + +func GetAll( + router chi.Router, + newsSvc newssvc.News, +) { + router.Get("/news", func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + news, err := newsSvc.GetAllModel() + if err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + err.Error(), + ) + return + } + + response.RespondJsonSuccess(ctx, w, news) + }) +} diff --git a/internal/api/http/news/module.go b/internal/api/http/news/module.go new file mode 100644 index 0000000..22ea939 --- /dev/null +++ b/internal/api/http/news/module.go @@ -0,0 +1,8 @@ +package newshttp + +import "go.uber.org/fx" + +var Module = fx.Module("news", fx.Invoke( + GetAll, + Create, +)) diff --git a/internal/api/http/router.go b/internal/api/http/router.go index 20f25f6..3ca226a 100644 --- a/internal/api/http/router.go +++ b/internal/api/http/router.go @@ -3,6 +3,7 @@ package internalhttp import ( authhttp "legalgo-BE-go/internal/api/http/auth" categoryhttp "legalgo-BE-go/internal/api/http/category" + newshttp "legalgo-BE-go/internal/api/http/news" subscribeplanhttp "legalgo-BE-go/internal/api/http/subscribe_plan" taghttp "legalgo-BE-go/internal/api/http/tag" @@ -23,6 +24,7 @@ var Module = fx.Module("router", subscribeplanhttp.Module, taghttp.Module, categoryhttp.Module, + newshttp.Module, ) func initRouter() chi.Router { diff --git a/internal/api/http/tag/get_all.go b/internal/api/http/tag/get_all.go index 9260cb7..2aecebf 100644 --- a/internal/api/http/tag/get_all.go +++ b/internal/api/http/tag/get_all.go @@ -14,7 +14,8 @@ func GetAll( ) { router.Get("/tag", func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - subsPlan, err := tagSvc.GetAll() + tags, err := tagSvc.GetAllModel() + // tags, err := tagSvc.GetAll() if err != nil { response.ResponseWithErrorCode( ctx, @@ -27,6 +28,6 @@ func GetAll( return } - response.RespondJsonSuccess(ctx, w, subsPlan) + response.RespondJsonSuccess(ctx, w, tags) }) } diff --git a/internal/domain/category/spec.go b/internal/domain/category/spec.go index 880b37f..773fa4b 100644 --- a/internal/domain/category/spec.go +++ b/internal/domain/category/spec.go @@ -1,7 +1,7 @@ package categorydomain type Category struct { - ID string `json:"id"` + ID string `json:"id" gorm:"primaryKey"` Name string `json:"name"` Code string `json:"code"` } diff --git a/internal/domain/news/request.go b/internal/domain/news/request.go deleted file mode 100644 index c679d3b..0000000 --- a/internal/domain/news/request.go +++ /dev/null @@ -1,12 +0,0 @@ -package newsdomain - -type News struct { - Title string `json:"title"` - Content string `json:"content"` - FeaturedImage string `json:"featured_image"` - Tags []string `json:"tags"` - Categories []string `json:"categories"` - IsPremium bool `json:"is_premium"` - Slug string `json:"slug"` - Author string `json:"author"` -} diff --git a/internal/domain/news/spec.go b/internal/domain/news/spec.go new file mode 100644 index 0000000..d3a66e8 --- /dev/null +++ b/internal/domain/news/spec.go @@ -0,0 +1,32 @@ +package newsdomain + +import ( + categorydomain "legalgo-BE-go/internal/domain/category" + tagdomain "legalgo-BE-go/internal/domain/tag" + "time" +) + +type NewsReq struct { + Title string `json:"title" validate:"required"` + Content string `json:"content"` + FeaturedImage string `json:"featured_image"` + Tags []string `json:"tags"` + Categories []string `json:"categories"` + IsPremium bool `json:"is_premium"` + LiveAt string `json:"live_at" validate:"required"` +} + +type News struct { + ID string `json:"id"` + Title string `json:"title"` + Content string `json:"content"` + FeaturedImage string `json:"featured_image"` + Tags []tagdomain.Tag `json:"tags"` + Categories []categorydomain.Category `json:"categories"` + IsPremium bool `json:"is_premium"` + Slug string `json:"slug"` + AuthorID string `json:"author_id"` + LiveAt time.Time `json:"live_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/internal/domain/tag/spec.go b/internal/domain/tag/spec.go index e766e1d..b57da47 100644 --- a/internal/domain/tag/spec.go +++ b/internal/domain/tag/spec.go @@ -6,7 +6,7 @@ type TagReq struct { } type Tag struct { - ID string `json:"id"` + ID string `json:"id" gorm:"primaryKey"` Code string `json:"code"` Name string `json:"name"` } diff --git a/internal/services/category/create.go b/internal/services/category/create.go index e11f47b..b8df50c 100644 --- a/internal/services/category/create.go +++ b/internal/services/category/create.go @@ -3,5 +3,6 @@ package categorysvc import categorydomain "legalgo-BE-go/internal/domain/category" func (i *impl) Create(spec categorydomain.CategoryReq) error { - return i.categoryRepo.Create(spec) + return i.categoryRepo.CreateModel(spec) + // return i.categoryRepo.Create(spec) } diff --git a/internal/services/category/get_all_model.go b/internal/services/category/get_all_model.go new file mode 100644 index 0000000..d44762a --- /dev/null +++ b/internal/services/category/get_all_model.go @@ -0,0 +1,9 @@ +package categorysvc + +import ( + "legalgo-BE-go/database" +) + +func (i *impl) GetAllModel() ([]database.CategoryModel, error) { + return i.categoryRepo.GetAllModel() +} diff --git a/internal/services/category/impl.go b/internal/services/category/impl.go index 1d97cd2..55ba775 100644 --- a/internal/services/category/impl.go +++ b/internal/services/category/impl.go @@ -1,6 +1,7 @@ package categorysvc import ( + "legalgo-BE-go/database" categoryrepository "legalgo-BE-go/internal/accessor/category" categorydomain "legalgo-BE-go/internal/domain/category" ) @@ -12,6 +13,7 @@ type impl struct { type Category interface { Create(categorydomain.CategoryReq) error GetAll() ([]categorydomain.Category, error) + GetAllModel() ([]database.CategoryModel, error) } func New( diff --git a/internal/services/module.go b/internal/services/module.go index 390a617..9ebc900 100644 --- a/internal/services/module.go +++ b/internal/services/module.go @@ -3,6 +3,7 @@ package services import ( serviceauth "legalgo-BE-go/internal/services/auth" categorysvc "legalgo-BE-go/internal/services/category" + newssvc "legalgo-BE-go/internal/services/news" subscribesvc "legalgo-BE-go/internal/services/subscribe" subscribeplansvc "legalgo-BE-go/internal/services/subscribe_plan" tagsvc "legalgo-BE-go/internal/services/tag" @@ -17,5 +18,6 @@ var Module = fx.Module("services", subscribesvc.New, tagsvc.New, categorysvc.New, + newssvc.New, ), ) diff --git a/internal/services/news/create.go b/internal/services/news/create.go new file mode 100644 index 0000000..fd8ee10 --- /dev/null +++ b/internal/services/news/create.go @@ -0,0 +1,47 @@ +package newssvc + +import ( + newsdomain "legalgo-BE-go/internal/domain/news" + "strings" + "time" + + "github.com/google/uuid" +) + +func (i *impl) Create(spec newsdomain.NewsReq, staffId string) error { + slug := strings.ToLower(strings.ReplaceAll(spec.Title, " ", "-")) + + tags, err := i.tagRepo.GetByIDs(spec.Tags) + if err != nil { + return err + } + + categories, err := i.categoryRepo.GetByIDs(spec.Categories) + if err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, spec.LiveAt) + if err != nil { + return err + } + + newSpec := &newsdomain.News{ + ID: uuid.NewString(), + Title: spec.Title, + Content: spec.Content, + FeaturedImage: spec.FeaturedImage, + IsPremium: spec.IsPremium, + Slug: slug, + LiveAt: parsedTime, + AuthorID: staffId, + Tags: tags, + Categories: categories, + } + + if err := i.newsRepo.Create(newSpec); err != nil { + return err + } + + return nil +} diff --git a/internal/services/news/create_model.go b/internal/services/news/create_model.go new file mode 100644 index 0000000..2dd5acd --- /dev/null +++ b/internal/services/news/create_model.go @@ -0,0 +1,48 @@ +package newssvc + +import ( + "legalgo-BE-go/database" + newsdomain "legalgo-BE-go/internal/domain/news" + "strings" + "time" + + "github.com/google/uuid" +) + +func (i *impl) CreateModel(spec newsdomain.NewsReq, staffId string) error { + slug := strings.ToLower(strings.ReplaceAll(spec.Title, " ", "-")) + + tags, err := i.tagRepo.GetBulks(spec.Tags) + if err != nil { + return err + } + + categories, err := i.categoryRepo.GetBulks(spec.Categories) + if err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, spec.LiveAt) + if err != nil { + return err + } + + newSpec := database.NewsModel{ + ID: uuid.NewString(), + Title: spec.Title, + Content: spec.Content, + FeaturedImage: spec.FeaturedImage, + IsPremium: spec.IsPremium, + Slug: slug, + LiveAt: parsedTime, + AuthorID: staffId, + Tags: tags, + Categories: categories, + } + + if err := i.newsRepo.CreateModel(newSpec); err != nil { + return err + } + + return nil +} diff --git a/internal/services/news/get_all.go b/internal/services/news/get_all.go new file mode 100644 index 0000000..0ceb6e1 --- /dev/null +++ b/internal/services/news/get_all.go @@ -0,0 +1,7 @@ +package newssvc + +import newsdomain "legalgo-BE-go/internal/domain/news" + +func (i *impl) GetAll() ([]newsdomain.News, error) { + return i.newsRepo.GetAll() +} diff --git a/internal/services/news/get_all_model.go b/internal/services/news/get_all_model.go new file mode 100644 index 0000000..cebb5fa --- /dev/null +++ b/internal/services/news/get_all_model.go @@ -0,0 +1,9 @@ +package newssvc + +import ( + "legalgo-BE-go/database" +) + +func (i *impl) GetAllModel() ([]database.NewsModel, error) { + return i.newsRepo.GetAllModel() +} diff --git a/internal/services/news/impl.go b/internal/services/news/impl.go new file mode 100644 index 0000000..ecdae09 --- /dev/null +++ b/internal/services/news/impl.go @@ -0,0 +1,34 @@ +package newssvc + +import ( + "legalgo-BE-go/database" + categoryrepository "legalgo-BE-go/internal/accessor/category" + newsrepository "legalgo-BE-go/internal/accessor/news" + tagrepository "legalgo-BE-go/internal/accessor/tag" + newsdomain "legalgo-BE-go/internal/domain/news" +) + +type impl struct { + newsRepo newsrepository.News + tagRepo tagrepository.TagAccessor + categoryRepo categoryrepository.Category +} + +type News interface { + GetAll() ([]newsdomain.News, error) + GetAllModel() ([]database.NewsModel, error) + Create(newsdomain.NewsReq, string) error + CreateModel(newsdomain.NewsReq, string) error +} + +func New( + newsRepo newsrepository.News, + tagRepo tagrepository.TagAccessor, + categoryRepo categoryrepository.Category, +) News { + return &impl{ + newsRepo, + tagRepo, + categoryRepo, + } +} diff --git a/internal/services/tag/create.go b/internal/services/tag/create.go index 7d314e4..658acb0 100644 --- a/internal/services/tag/create.go +++ b/internal/services/tag/create.go @@ -3,5 +3,6 @@ package tagsvc import tagdomain "legalgo-BE-go/internal/domain/tag" func (i *impl) Create(spec tagdomain.TagReq) error { - return i.tagRepo.Create(spec) + // return i.tagRepo.Create(spec) + return i.tagRepo.CreateModel(spec) } diff --git a/internal/services/tag/get_all_model.go b/internal/services/tag/get_all_model.go new file mode 100644 index 0000000..8fa0e54 --- /dev/null +++ b/internal/services/tag/get_all_model.go @@ -0,0 +1,9 @@ +package tagsvc + +import ( + "legalgo-BE-go/database" +) + +func (i *impl) GetAllModel() ([]database.TagModel, error) { + return i.tagRepo.GetAllModel() +} diff --git a/internal/services/tag/impl.go b/internal/services/tag/impl.go index ac34c5c..b0f6294 100644 --- a/internal/services/tag/impl.go +++ b/internal/services/tag/impl.go @@ -1,6 +1,7 @@ package tagsvc import ( + "legalgo-BE-go/database" tagrepository "legalgo-BE-go/internal/accessor/tag" tagdomain "legalgo-BE-go/internal/domain/tag" ) @@ -12,6 +13,7 @@ type impl struct { type TagIntf interface { Create(tagdomain.TagReq) error GetAll() ([]tagdomain.Tag, error) + GetAllModel() ([]database.TagModel, error) } func New( diff --git a/internal/utilities/utils/get_token_detail.go b/internal/utilities/utils/get_token_detail.go new file mode 100644 index 0000000..82fefbd --- /dev/null +++ b/internal/utilities/utils/get_token_detail.go @@ -0,0 +1,34 @@ +package utils + +import ( + "errors" + authdomain "legalgo-BE-go/internal/domain/auth" + "net/http" + "strings" +) + +func GetTokenDetail(r *http.Request) (authdomain.AuthToken, error) { + authHeader := r.Header.Get("Authorization") + + var data authdomain.AuthToken + + if authHeader == "" { + return data, errors.New("unauthorized") + } + + if !strings.HasPrefix(authHeader, "Bearer") { + return data, errors.New("invalid token") + } + + token := strings.Split(authHeader, " ") + if len(token) < 2 { + return data, errors.New("invalid token") + } + + data, err := DestructToken(token[1]) + if err != nil { + return data, err + } + + return data, nil +} diff --git a/openapi.yml b/openapi.yml index ef2c932..0d159de 100644 --- a/openapi.yml +++ b/openapi.yml @@ -10,13 +10,6 @@ paths: summary: "get staff profile" tags: - Staff - parameters: - - name: "id" - in: query - description: "staff id" - required: true - schema: - type: string responses: "200": description: Success get profile @@ -169,13 +162,6 @@ paths: summary: "get staff profile" tags: - User - parameters: - - name: "id" - in: query - description: "user id" - required: true - schema: - type: string responses: "200": description: Success get profile @@ -574,7 +560,7 @@ paths: post: summary: Create a new category tags: - - Tags + - Category requestBody: required: true content: @@ -631,3 +617,177 @@ paths: type: string message: type: string + + /news/create: + post: + summary: Create a new news article + tags: + - News + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + title: + type: string + content: + type: string + featured_image: + type: string + format: uri + tags: + type: array + items: + type: string + categories: + type: array + items: + type: string + is_premium: + type: boolean + live_at: + type: string + format: date-time + required: + - title + - content + - featured_image + - tags + - categories + - is_premium + - live_at + responses: + "201": + description: News article created + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + message: + type: string + example: "news created successfully." + "400": + description: Bad request + content: + application/json: + schema: + type: object + properties: + error: + type: object + properties: + code: + type: string + message: + type: string + "409": + description: Conflict (e.g., duplicate title or invalid tag/category) + content: + application/json: + schema: + type: object + properties: + error: + type: object + properties: + code: + type: string + message: + type: string + + /news: + get: + summary: Get all news articles + tags: + - News + responses: + "200": + description: Successfully retrieved all news articles + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + type: object + properties: + id: + type: string + title: + type: string + content: + type: string + categories: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + code: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + tags: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + code: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + is_premium: + type: boolean + slug: + type: string + featured_image: + type: string + format: uri + author_id: + type: string + live_at: + type: string + format: date-time + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + "400": + description: Bad request + content: + application/json: + schema: + type: object + properties: + error: + type: object + properties: + code: + type: string + message: + type: string