diff --git a/.gitignore b/.gitignore index aaff602..01f36f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ bin .env .DS_Store +/cmd/legalgo/env diff --git a/database/staff_model.go b/database/staff_model.go index db0e734..708d641 100644 --- a/database/staff_model.go +++ b/database/staff_model.go @@ -6,7 +6,7 @@ import ( type Staff struct { ID string `gorm:"primaryKey" json:"id"` - Name string `gorm:"default:null;unique" json:"name"` + Name string `gorm:"default:null" json:"name"` ProfilePicture string `gorm:"default:null" json:"profile_picture"` Email string `gorm:"unique;not null" json:"email"` Password string `gorm:"not null" json:"password"` diff --git a/internal/accessor/news/impl.go b/internal/accessor/news/impl.go index da8f4b3..1948ac3 100644 --- a/internal/accessor/news/impl.go +++ b/internal/accessor/news/impl.go @@ -13,6 +13,7 @@ type News interface { GetAll() ([]newsdomain.News, error) GetBySlug(string) (*newsdomain.News, error) Create(newsdomain.News) error + Update(newsdomain.News) error Delete(string) error } diff --git a/internal/accessor/news/update.go b/internal/accessor/news/update.go new file mode 100644 index 0000000..c884806 --- /dev/null +++ b/internal/accessor/news/update.go @@ -0,0 +1,66 @@ +package newsrepository + +import ( + "fmt" + newsdomain "legalgo-BE-go/internal/domain/news" + + "gorm.io/gorm/clause" +) + +func (a *accessor) Update(spec newsdomain.News) error { + tx := a.db.Begin() + if err := tx.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "id"}}, + DoUpdates: clause.AssignmentColumns([]string{ + "title", + "content", + "featured_image", + "is_premium", + "slug", + "author_id", + "live_at", + "updated_at", + }), + }).Select( + "title", + "content", + "featured_image", + "is_premium", + "slug", + "author_id", + "live_at", + "updated_at", + ).Save(&spec).Error; err != nil { + tx.Rollback() + return fmt.Errorf("failed to update news: %v", err) + } + if len(spec.Tags) < 1 { + if err := tx.Model(&spec).Association("Tags").Clear(); err != nil { + tx.Rollback() + return fmt.Errorf("failed to clear tags: %v", err) + } + } + + if len(spec.Categories) < 1 { + if err := tx.Model(&spec).Association("Categories").Clear(); err != nil { + tx.Rollback() + return fmt.Errorf("failed to clear categories: %v", err) + } + } + + if err := tx.Model(&spec).Association("Tags").Append(spec.Tags); err != nil { + tx.Rollback() + return fmt.Errorf("failed to add tags: %v", err) + } + + if err := tx.Model(&spec).Association("Categories").Append(spec.Categories); err != nil { + tx.Rollback() + return fmt.Errorf("failed to add categories: %v", err) + } + + if err := tx.Commit().Error; err != nil { + return fmt.Errorf("failed to commit transaction: %v", err) + } + + return nil +} diff --git a/internal/accessor/user_repository/create_user.go b/internal/accessor/user_repository/create_user.go index bbd0d6e..69184ec 100644 --- a/internal/accessor/user_repository/create_user.go +++ b/internal/accessor/user_repository/create_user.go @@ -4,8 +4,8 @@ import ( userdomain "legalgo-BE-go/internal/domain/user" ) -func (ur *UserRepository) CreateUser(spec userdomain.User) error { - if err := ur.DB.Create(&spec).Error; err != nil { +func (ur *accessor) CreateUser(spec userdomain.User) error { + if err := ur.db.Create(&spec).Error; err != nil { return err } diff --git a/internal/accessor/user_repository/get_user_by_email.go b/internal/accessor/user_repository/get_user_by_email.go index ada5ec5..5a37d91 100644 --- a/internal/accessor/user_repository/get_user_by_email.go +++ b/internal/accessor/user_repository/get_user_by_email.go @@ -7,14 +7,14 @@ import ( "gorm.io/gorm" ) -func (ur *UserRepository) GetUserByEmail(email string) (*userdomain.User, error) { +func (ur *accessor) GetUserByEmail(email string) (*userdomain.User, error) { var user *userdomain.User if email == "" { return nil, errors.New("email is empty") } - if err := ur.DB.First(&user, "email = ?", email).Error; err != nil { + if err := ur.db.First(&user, "email = ?", email).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("user not found") } diff --git a/internal/accessor/user_repository/get_user_by_id.go b/internal/accessor/user_repository/get_user_by_id.go index 7c6c6f5..c83336f 100644 --- a/internal/accessor/user_repository/get_user_by_id.go +++ b/internal/accessor/user_repository/get_user_by_id.go @@ -6,17 +6,17 @@ import ( userdomain "legalgo-BE-go/internal/domain/user" ) -func (ur *UserRepository) GetUserByID(email string) (*userdomain.User, error) { +func (ur *accessor) GetUserByID(id string) (*userdomain.User, error) { var user userdomain.User - if email == "" { - return nil, errors.New("email is empty") + if id == "" { + return nil, errors.New("id is empty") } - if err := ur.DB. + if err := ur.db. Preload("Subscribe"). Preload("Subscribe.SubscribePlan"). - First(&user, "email = ?", email).Error; err != nil { + First(&user, "id = ?", id).Error; err != nil { return nil, err } diff --git a/internal/accessor/user_repository/get_user_profile.go b/internal/accessor/user_repository/get_user_profile.go index 2f18792..34e6a56 100644 --- a/internal/accessor/user_repository/get_user_profile.go +++ b/internal/accessor/user_repository/get_user_profile.go @@ -5,14 +5,14 @@ import ( userdomain "legalgo-BE-go/internal/domain/user" ) -func (ur *UserRepository) GetUserProfile(email string) (*userdomain.UserProfile, error) { +func (ur *accessor) GetUserProfile(email string) (*userdomain.UserProfile, error) { var user *userdomain.User if email == "" { return nil, errors.New("email is empty") } - if err := ur.DB. + if err := ur.db. Preload("Subscribe"). Preload("Subscribe.SubscribePlan"). First(&user, "email = ?", email). diff --git a/internal/accessor/user_repository/impl.go b/internal/accessor/user_repository/impl.go index c0e428a..52e869e 100644 --- a/internal/accessor/user_repository/impl.go +++ b/internal/accessor/user_repository/impl.go @@ -5,11 +5,11 @@ import ( userdomain "legalgo-BE-go/internal/domain/user" ) -type UserRepository struct { - DB *database.DB +type accessor struct { + db *database.DB } -type UserIntf interface { +type User interface { GetUserByEmail(string) (*userdomain.User, error) GetUserByID(string) (*userdomain.User, error) GetUserProfile(string) (*userdomain.UserProfile, error) @@ -18,6 +18,6 @@ type UserIntf interface { func New( db *database.DB, -) UserIntf { - return &UserRepository{db} +) User { + return &accessor{db} } diff --git a/internal/api/http/news/module.go b/internal/api/http/news/module.go index 2756220..14f5e73 100644 --- a/internal/api/http/news/module.go +++ b/internal/api/http/news/module.go @@ -6,5 +6,6 @@ var Module = fx.Module("news", fx.Invoke( GetAll, GetBySlug, Create, + Update, Delete, )) diff --git a/internal/api/http/news/update.go b/internal/api/http/news/update.go new file mode 100644 index 0000000..e86a2f5 --- /dev/null +++ b/internal/api/http/news/update.go @@ -0,0 +1,97 @@ +package newshttp + +import ( + "fmt" + authmiddleware "legalgo-BE-go/internal/api/http/middleware/auth" + newsdomain "legalgo-BE-go/internal/domain/news" + authsvc "legalgo-BE-go/internal/services/auth" + 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" +) + +func Update( + router chi.Router, + newsSvc newssvc.News, + authSvc authsvc.Auth, +) { + router.With(authmiddleware.Authorize()). + Put("/news/{news_id}/update", func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + newsID := chi.URLParam(r, "news_id") + + if newsID == "" { + response.ResponseWithErrorCode( + ctx, + w, + fmt.Errorf("news id is not provided"), + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + "news id is not provided", + ) + return + } + + destructedToken, err := utils.GetTokenDetail(r) + if err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + err.Error(), + ) + return + } + + staff, err := authSvc.GetStaffProfile(destructedToken.Email) + if err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + err.Error(), + ) + return + } + + var spec newsdomain.NewsUpdate + if err := utils.UnmarshalBody(r, &spec); err != nil { + response.ResponseWithErrorCode( + ctx, + w, + err, + response.ErrBadRequest.Code, + response.ErrBadRequest.HttpCode, + err.Error(), + ) + return + } + + spec.ID = newsID + + if err := newsSvc.Update(staff.ID, spec); 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 updated successfully.", + }) + }) +} diff --git a/internal/domain/news/spec.go b/internal/domain/news/spec.go index 9d6dc13..2c6c1a9 100644 --- a/internal/domain/news/spec.go +++ b/internal/domain/news/spec.go @@ -38,3 +38,14 @@ type News struct { Author Staff `json:"author"` } + +type NewsUpdate struct { + ID string `json:"id"` + 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"` + LiveAt time.Time `json:"live_at"` +} diff --git a/internal/services/auth/impl.go b/internal/services/auth/impl.go index ef38806..fe7e766 100644 --- a/internal/services/auth/impl.go +++ b/internal/services/auth/impl.go @@ -11,7 +11,7 @@ import ( type impl struct { staffRepo staffrepository.Staff - userRepo userrepository.UserIntf + userRepo userrepository.User subsRepo subscriberepository.SubsIntf subsPlanRepo subscribeplanrepository.SubsPlanIntf } @@ -29,7 +29,7 @@ type Auth interface { func New( staffRepo staffrepository.Staff, - userRepo userrepository.UserIntf, + userRepo userrepository.User, subsRepo subscriberepository.SubsIntf, subsPlanRepo subscribeplanrepository.SubsPlanIntf, ) Auth { diff --git a/internal/services/news/create.go b/internal/services/news/create.go index 517ebef..656be4e 100644 --- a/internal/services/news/create.go +++ b/internal/services/news/create.go @@ -2,14 +2,14 @@ package newssvc import ( newsdomain "legalgo-BE-go/internal/domain/news" - "strings" + "legalgo-BE-go/internal/utilities/utils" "time" "github.com/google/uuid" ) func (i *impl) Create(spec newsdomain.NewsReq, staffId string) error { - slug := strings.ToLower(strings.ReplaceAll(spec.Title, " ", "-")) + slug := utils.TitleToSlug(spec.Title) tags, err := i.tagRepo.GetByIDs(spec.Tags) if err != nil { diff --git a/internal/services/news/impl.go b/internal/services/news/impl.go index 675e527..c4ed51d 100644 --- a/internal/services/news/impl.go +++ b/internal/services/news/impl.go @@ -4,6 +4,7 @@ import ( categoryrepository "legalgo-BE-go/internal/accessor/category" newsrepository "legalgo-BE-go/internal/accessor/news" tagrepository "legalgo-BE-go/internal/accessor/tag" + userrepository "legalgo-BE-go/internal/accessor/user_repository" newsdomain "legalgo-BE-go/internal/domain/news" ) @@ -11,12 +12,14 @@ type impl struct { newsRepo newsrepository.News tagRepo tagrepository.TagAccessor categoryRepo categoryrepository.Category + userRepo userrepository.User } type News interface { GetAll() ([]newsdomain.News, error) GetBySlug(string) (*newsdomain.News, error) Create(newsdomain.NewsReq, string) error + Update(string, newsdomain.NewsUpdate) error Delete(string) error } @@ -24,10 +27,12 @@ func New( newsRepo newsrepository.News, tagRepo tagrepository.TagAccessor, categoryRepo categoryrepository.Category, + userRepo userrepository.User, ) News { return &impl{ newsRepo, tagRepo, categoryRepo, + userRepo, } } diff --git a/internal/services/news/update.go b/internal/services/news/update.go new file mode 100644 index 0000000..48d1929 --- /dev/null +++ b/internal/services/news/update.go @@ -0,0 +1,39 @@ +package newssvc + +import ( + newsdomain "legalgo-BE-go/internal/domain/news" + timeutils "legalgo-BE-go/internal/utilities/time_utils" + "legalgo-BE-go/internal/utilities/utils" +) + +func (i *impl) Update(id string, spec newsdomain.NewsUpdate) error { + tags, err := i.tagRepo.GetByIDs(spec.Tags) + if err != nil { + return err + } + + categories, err := i.categoryRepo.GetByIDs(spec.Categories) + if err != nil { + return err + } + + newSpec := newsdomain.News{ + ID: spec.ID, + AuthorID: id, + UpdatedAt: timeutils.Now(), + Slug: utils.TitleToSlug(spec.Title), + LiveAt: spec.LiveAt, + Title: spec.Title, + IsPremium: spec.IsPremium, + Content: spec.Content, + FeaturedImage: spec.FeaturedImage, + Tags: tags, + Categories: categories, + } + + if err := i.newsRepo.Update(newSpec); err != nil { + return err + } + + return nil +} diff --git a/internal/utilities/utils/title_to_slug.go b/internal/utilities/utils/title_to_slug.go new file mode 100644 index 0000000..7848b50 --- /dev/null +++ b/internal/utilities/utils/title_to_slug.go @@ -0,0 +1,7 @@ +package utils + +import "strings" + +func TitleToSlug(title string) string { + return strings.ToLower(strings.ReplaceAll(title, " ", "-")) +}