Compare commits

...

6 Commits

Author SHA1 Message Date
ericprd
915f12eaf7 feat: news router 2025-03-02 04:36:17 +08:00
ericprd
f4ae93bc48 fix: condition query 2025-03-02 02:33:03 +08:00
ericprd
eb1584f8c4 fix: unique entity 2025-03-01 21:27:28 +08:00
ericprd
bdf6dd131e feat: category routes 2025-03-01 21:20:51 +08:00
ericprd
acd80480dd feat: tag routes 2025-03-01 20:00:54 +08:00
ericprd
613379c8a5 fix: simplified gorm query 2025-03-01 19:00:12 +08:00
69 changed files with 1566 additions and 187 deletions

22
.vscode/launch.json vendored Normal file
View File

@ -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": ""
}
}]
}

View File

@ -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)
}

View File

@ -0,0 +1,21 @@
package database
import (
"time"
)
type Category struct {
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"`
}

View File

@ -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,5 +55,11 @@ func (db *DB) Migrate() error {
&SubscribePlan{},
&Subscribe{},
&User{},
// &Tag{},
// &Category{},
// &News{},
&NewsModel{},
&TagModel{},
&CategoryModel{},
)
}

View File

@ -6,12 +6,30 @@ 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"`
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"`
}

View File

@ -6,8 +6,8 @@ import (
type Staff struct {
ID string `gorm:"primaryKey" json:"id"`
Username string `gorm:"default:null" json:"username"`
Email string `gorm:"unique,not null" json:"email"`
Username string `gorm:"default:null;unique" json:"username"`
Email string `gorm:"unique;not null" json:"email"`
Password string `gorm:"not null" json:"password"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`

View File

@ -19,7 +19,7 @@ type Subscribe struct {
type SubscribePlan struct {
ID string `gorm:"primaryKey" json:"id"`
Code string `gorm:"not null" json:"code"`
Code string `gorm:"not null;unique" json:"code"`
Name string `gorm:"not null" json:"name"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`

21
database/tag_module.go Normal file
View File

@ -0,0 +1,21 @@
package database
import (
"time"
)
type Tag struct {
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"`
}

View File

@ -1,13 +0,0 @@
package database
import (
"time"
)
type Tags struct {
ID string `gorm:"primaryKey;not null" json:"id"`
Code string `gorm:"not null" 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"`
}

View File

@ -5,11 +5,11 @@ 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"`
Email string `gorm:"unique;not null" json:"email"`
Password string `gorm:"not null" json:"password"`
Phone string `gorm:"default:null" json:"phone"`
Phone string `gorm:"default:not null;unique" json:"phone"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`

View File

@ -0,0 +1,21 @@
package categoryrepository
import (
categorydomain "legalgo-BE-go/internal/domain/category"
"github.com/google/uuid"
)
func (a *accessor) Create(spec categorydomain.CategoryReq) error {
data := categorydomain.Category{
ID: uuid.NewString(),
Name: spec.Name,
Code: spec.Code,
}
if err := a.DB.Create(&data).Error; err != nil {
return err
}
return nil
}

View File

@ -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
}

View File

@ -0,0 +1,13 @@
package categoryrepository
import categorydomain "legalgo-BE-go/internal/domain/category"
func (a *accessor) GetAll() ([]categorydomain.Category, error) {
var categories []categorydomain.Category
if err := a.DB.Find(&categories).Error; err != nil {
return nil, err
}
return categories, nil
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,27 @@
package categoryrepository
import (
"legalgo-BE-go/database"
categorydomain "legalgo-BE-go/internal/domain/category"
)
type accessor struct {
DB *database.DB
}
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(
db *database.DB,
) Category {
return &accessor{db}
}

View File

@ -1,10 +1,13 @@
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"
subscribeplanrepository "legalgo-BE-go/internal/accessor/subscribeplan"
tagrepository "legalgo-BE-go/internal/accessor/tag"
userrepository "legalgo-BE-go/internal/accessor/user_repository"
"go.uber.org/fx"
@ -16,4 +19,7 @@ var Module = fx.Module("repository", fx.Provide(
redisaccessor.New,
subscribeplanrepository.New,
subscriberepository.New,
tagrepository.New,
categoryrepository.New,
newsrepository.New,
))

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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}
}

View File

@ -15,7 +15,7 @@ func (sr *StaffRepository) GetStaffByEmail(email string) (*authdomain.Staff, err
return nil, errors.New("email is required")
}
if err := sr.DB.Where("email = ?", email).First(&staff).Error; err != nil {
if err := sr.DB.First(&staff, "email = ?", email).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("staff not found")
}

View File

@ -14,7 +14,7 @@ func (sr *StaffRepository) GetStaffByID(ID string) (*authdomain.Staff, error) {
return nil, errors.New("id is required")
}
if err := sr.DB.Where("id = ? ", ID).First(&staff).Error; err != nil {
if err := sr.DB.First(&staff, "id = ? ", ID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("staff not found")
}

View File

@ -9,7 +9,7 @@ import (
func (s *SubsPlan) GetDefault() (subscribeplandomain.SubscribePlan, error) {
var subscribePlan subscribeplandomain.SubscribePlan
if err := s.DB.Where("code = ?", "basic").First(&subscribePlan).Error; err != nil {
if err := s.DB.First(&subscribePlan, "code = ?", "basic").Error; err != nil {
s.DB.Create(&subscribeplandomain.SubscribePlan{
ID: uuid.NewString(),
Code: "basic",

View File

@ -0,0 +1,21 @@
package tagrepository
import (
tagdomain "legalgo-BE-go/internal/domain/tag"
"github.com/google/uuid"
)
func (acc *accessor) Create(spec tagdomain.TagReq) error {
data := &tagdomain.Tag{
ID: uuid.NewString(),
Code: spec.Code,
Name: spec.Name,
}
if err := acc.DB.Create(&data).Error; err != nil {
return err
}
return nil
}

View File

@ -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
}

View File

@ -0,0 +1,15 @@
package tagrepository
import (
tagdomain "legalgo-BE-go/internal/domain/tag"
)
func (acc *accessor) GetAll() ([]tagdomain.Tag, error) {
var tags []tagdomain.Tag
if err := acc.DB.Find(&tags).Error; err != nil {
return nil, err
}
return tags, nil
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,25 @@
package tagrepository
import (
"legalgo-BE-go/database"
tagdomain "legalgo-BE-go/internal/domain/tag"
)
type accessor struct {
DB *database.DB
}
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(
db *database.DB,
) TagAccessor {
return &accessor{db}
}

View File

@ -15,7 +15,7 @@ func (ur *UserRepository) GetUserByEmail(email string) (*authdomain.User, error)
return nil, errors.New("email is empty")
}
if err := ur.DB.Where("email = ?", email).First(&user).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")
}

View File

@ -15,6 +15,7 @@ func (ur *UserRepository) GetUserProfile(email string) (*authdomain.UserProfile,
}
if err := ur.DB.Table("users u").
Where("email = ?", email).
Select("u.email, u.id, s.status as subscribe_status, sp.code as subscribe_plan_code, sp.name as subscribe_plan_name").
Joins("join subscribes s on s.id = u.subscribe_id").
Joins("join subscribe_plans sp on s.subscribe_plan_id = sp.id").

View File

@ -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,

View File

@ -0,0 +1,67 @@
package categoryhttp
import (
categorydomain "legalgo-BE-go/internal/domain/category"
categorysvc "legalgo-BE-go/internal/services/category"
"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(
router chi.Router,
validate *validator.Validate,
categorySvc categorysvc.Category,
) {
router.Post("/category/create", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var spec categorydomain.CategoryReq
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
}
if err := categorySvc.Create(spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrCreateEntity.Code,
response.ErrCreateEntity.HttpCode,
err.Error(),
)
return
}
response.RespondJsonSuccess(ctx, w, struct {
Message string
}{
Message: "category created successfully",
})
})
}

View File

@ -0,0 +1,33 @@
package categoryhttp
import (
categorysvc "legalgo-BE-go/internal/services/category"
"legalgo-BE-go/internal/utilities/response"
"net/http"
"github.com/go-chi/chi/v5"
)
func GetAll(
router chi.Router,
categorySvc categorysvc.Category,
) {
router.Get("/category", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
subsPlan, err := categorySvc.GetAllModel()
// subsPlan, err := categorySvc.GetAll()
if err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
err.Error(),
)
return
}
response.RespondJsonSuccess(ctx, w, subsPlan)
})
}

View File

@ -0,0 +1,8 @@
package categoryhttp
import "go.uber.org/fx"
var Module = fx.Module("categories", fx.Invoke(
Create,
GetAll,
))

View File

@ -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.",
})
})
}

View File

@ -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)
})
}

View File

@ -0,0 +1,8 @@
package newshttp
import "go.uber.org/fx"
var Module = fx.Module("news", fx.Invoke(
GetAll,
Create,
))

View File

@ -2,7 +2,10 @@ 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"
"github.com/go-chi/chi/v5"
"github.com/go-chi/cors"
@ -19,6 +22,9 @@ var Module = fx.Module("router",
),
authhttp.Module,
subscribeplanhttp.Module,
taghttp.Module,
categoryhttp.Module,
newshttp.Module,
)
func initRouter() chi.Router {

View File

@ -0,0 +1,67 @@
package taghttp
import (
"net/http"
tagdomain "legalgo-BE-go/internal/domain/tag"
tagsvc "legalgo-BE-go/internal/services/tag"
"legalgo-BE-go/internal/utilities/response"
"legalgo-BE-go/internal/utilities/utils"
"github.com/go-chi/chi/v5"
"github.com/go-playground/validator/v10"
)
func Create(
router chi.Router,
validate *validator.Validate,
tagSvc tagsvc.TagIntf,
) {
router.Post("/tag/create", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var spec tagdomain.TagReq
if err := utils.UnmarshalBody(r, &spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
"failed to unmarshal request",
)
return
}
if err := validate.Struct(spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
err.(validator.ValidationErrors).Error(),
)
return
}
if err := tagSvc.Create(spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrCreateEntity.Code,
response.ErrCreateEntity.HttpCode,
err.Error(),
)
return
}
response.RespondJsonSuccess(ctx, w, struct {
Message string
}{
Message: "tag created successfully.",
})
})
}

View File

@ -0,0 +1,33 @@
package taghttp
import (
tagsvc "legalgo-BE-go/internal/services/tag"
"legalgo-BE-go/internal/utilities/response"
"net/http"
"github.com/go-chi/chi/v5"
)
func GetAll(
router chi.Router,
tagSvc tagsvc.TagIntf,
) {
router.Get("/tag", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
tags, err := tagSvc.GetAllModel()
// tags, err := tagSvc.GetAll()
if err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
err.Error(),
)
return
}
response.RespondJsonSuccess(ctx, w, tags)
})
}

View File

@ -0,0 +1,8 @@
package taghttp
import "go.uber.org/fx"
var Module = fx.Module("tag", fx.Invoke(
Create,
GetAll,
))

View File

@ -3,7 +3,7 @@ package authdomain
type RegisterUserReq struct {
Email string `json:"email" validate:"required"`
Password string `json:"password" validate:"required"`
Phone string `json:"phone"`
Phone string `json:"phone" validate:"required"`
SubscribePlanID string `json:"subscribe_plan_id"`
}

View File

@ -0,0 +1,12 @@
package categorydomain
type Category struct {
ID string `json:"id" gorm:"primaryKey"`
Name string `json:"name"`
Code string `json:"code"`
}
type CategoryReq struct {
Name string `json:"name" validate:"required"`
Code string `json:"code" validate:"required"`
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -0,0 +1,12 @@
package tagdomain
type TagReq struct {
Code string `json:"code" validate:"required"`
Name string `json:"name" validate:"required"`
}
type Tag struct {
ID string `json:"id" gorm:"primaryKey"`
Code string `json:"code"`
Name string `json:"name"`
}

View File

@ -0,0 +1,8 @@
package categorysvc
import categorydomain "legalgo-BE-go/internal/domain/category"
func (i *impl) Create(spec categorydomain.CategoryReq) error {
return i.categoryRepo.CreateModel(spec)
// return i.categoryRepo.Create(spec)
}

View File

@ -0,0 +1,7 @@
package categorysvc
import categorydomain "legalgo-BE-go/internal/domain/category"
func (i *impl) GetAll() ([]categorydomain.Category, error) {
return i.categoryRepo.GetAll()
}

View File

@ -0,0 +1,9 @@
package categorysvc
import (
"legalgo-BE-go/database"
)
func (i *impl) GetAllModel() ([]database.CategoryModel, error) {
return i.categoryRepo.GetAllModel()
}

View File

@ -0,0 +1,25 @@
package categorysvc
import (
"legalgo-BE-go/database"
categoryrepository "legalgo-BE-go/internal/accessor/category"
categorydomain "legalgo-BE-go/internal/domain/category"
)
type impl struct {
categoryRepo categoryrepository.Category
}
type Category interface {
Create(categorydomain.CategoryReq) error
GetAll() ([]categorydomain.Category, error)
GetAllModel() ([]database.CategoryModel, error)
}
func New(
categoryRepo categoryrepository.Category,
) Category {
return &impl{
categoryRepo: categoryRepo,
}
}

View File

@ -2,8 +2,11 @@ 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"
"go.uber.org/fx"
)
@ -13,5 +16,8 @@ var Module = fx.Module("services",
serviceauth.New,
subscribeplansvc.New,
subscribesvc.New,
tagsvc.New,
categorysvc.New,
newssvc.New,
),
)

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -0,0 +1,9 @@
package newssvc
import (
"legalgo-BE-go/database"
)
func (i *impl) GetAllModel() ([]database.NewsModel, error) {
return i.newsRepo.GetAllModel()
}

View File

@ -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,
}
}

View File

@ -5,10 +5,5 @@ import (
)
func (s *SubsPlanSvc) GetAllPlan() ([]subscribeplandomain.SubscribePlan, error) {
subsPlan, err := s.subsAccs.GetAll()
if err != nil {
return nil, err
}
return subsPlan, nil
return s.subsAccs.GetAll()
}

View File

@ -0,0 +1,8 @@
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.CreateModel(spec)
}

View File

@ -0,0 +1,7 @@
package tagsvc
import tagdomain "legalgo-BE-go/internal/domain/tag"
func (i *impl) GetAll() ([]tagdomain.Tag, error) {
return i.tagRepo.GetAll()
}

View File

@ -0,0 +1,9 @@
package tagsvc
import (
"legalgo-BE-go/database"
)
func (i *impl) GetAllModel() ([]database.TagModel, error) {
return i.tagRepo.GetAllModel()
}

View File

@ -0,0 +1,23 @@
package tagsvc
import (
"legalgo-BE-go/database"
tagrepository "legalgo-BE-go/internal/accessor/tag"
tagdomain "legalgo-BE-go/internal/domain/tag"
)
type impl struct {
tagRepo tagrepository.TagAccessor
}
type TagIntf interface {
Create(tagdomain.TagReq) error
GetAll() ([]tagdomain.Tag, error)
GetAllModel() ([]database.TagModel, error)
}
func New(
tagRepo tagrepository.TagAccessor,
) TagIntf {
return &impl{tagRepo}
}

View File

@ -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
}

View File

@ -17,9 +17,12 @@ build-migrate:
@echo "Building the Migrate..."
go build -o $(OUTPUT_MIGRATE_DIR) $(BINARY_MIGRATE_NAME)
migrate: build-migrate
@echo "Building and running Migrate..."
$(OUTPUT_MIGRATE_DIR)
# migrate: build-migrate
# @echo "Building and running Migrate..."
# $(OUTPUT_MIGRATE_DIR)
migrate:
go run $(BINARY_MIGRATE_NAME)
clean:
@echo "Cleaning the build..."

View File

@ -5,6 +5,43 @@ info:
description: API for handling staff and user login, registration, and subscription plan creation.
paths:
/staff/profile:
get:
summary: "get staff profile"
tags:
- Staff
responses:
"200":
description: Success get profile
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
id:
type: string
email:
type: string
username:
type: string
"400":
description: Bad request
content:
application/json:
schema:
type: object
properties:
error:
type: object
properties:
code:
type: string
message:
type: string
/staff/login:
post:
summary: Login for staff
@ -120,62 +157,11 @@ paths:
message:
type: string
/staff/profile:
get:
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
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
id:
type: string
email:
type: string
username:
type: string
"400":
description: Bad request
content:
application/json:
schema:
type: object
properties:
error:
type: object
properties:
code:
type: string
message:
type: string
/user/profile:
get:
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
@ -429,3 +415,379 @@ paths:
type: string
message:
type: string
/tag:
get:
summary: Get all tags
tags:
- Tags
responses:
"200":
description: Successfully retrieved all tags
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
type: object
properties:
id:
type: string
code:
type: string
name:
type: string
"400":
description: Bad request
content:
application/json:
schema:
type: object
properties:
error:
type: object
properties:
code:
type: string
message:
type: string
/tag/create:
post:
summary: Create a new tag
tags:
- Tags
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
code:
type: string
required:
- code
- name
responses:
"201":
description: Tag created
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
message:
type: string
example: "tag 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 (tag code already exists)
content:
application/json:
schema:
type: object
properties:
error:
type: object
properties:
code:
type: string
message:
type: string
/category:
get:
summary: Get all categories
tags:
- Category
responses:
"200":
description: Successfully retrieved all categories
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
type: object
properties:
id:
type: string
code:
type: string
name:
type: string
"400":
description: Bad request
content:
application/json:
schema:
type: object
properties:
error:
type: object
properties:
code:
type: string
message:
type: string
/category/create:
post:
summary: Create a new category
tags:
- Category
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
code:
type: string
required:
- code
- name
responses:
"201":
description: Category created
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
message:
type: string
example: "category 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 (category code already exists)
content:
application/json:
schema:
type: object
properties:
error:
type: object
properties:
code:
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