diff --git a/common/skill.go b/common/skill.go new file mode 100644 index 0000000..65d67b7 --- /dev/null +++ b/common/skill.go @@ -0,0 +1,34 @@ +package common + +type SkillCreationInput struct { + Name string `json:"name"` +} + +type SkillUpdateInput struct { + Name string `json:"name"` +} + +type SkillGroupCreationInput struct { + Name string `json:"name"` +} + +type SkillGroupUpdateInput struct { + Name string `json:"name"` + Skills []int `json:"skills"` +} + +func NewSkillCreationInput() *SkillCreationInput { + return &SkillCreationInput{} +} + +func NewSkillUpdateInput() *SkillUpdateInput { + return &SkillUpdateInput{} +} + +func NewSkillGroupCreationInput() *SkillGroupCreationInput { + return &SkillGroupCreationInput{} +} + +func NewSkillGroupUpdateInput() *SkillGroupUpdateInput { + return &SkillGroupUpdateInput{} +} diff --git a/common/user.go b/common/user.go index e28d3a7..4e73d47 100644 --- a/common/user.go +++ b/common/user.go @@ -4,6 +4,7 @@ type UserCreationInput struct { FullName string `json:"fullName"` Email string `json:"email"` } + type UserUpdateInput struct { FullName string `json:"fullName"` Email string `json:"email"` diff --git a/handlers/skill.go b/handlers/skill.go new file mode 100644 index 0000000..87fbfca --- /dev/null +++ b/handlers/skill.go @@ -0,0 +1,232 @@ +package handlers + +import ( + "fmt" + "net/http" + + "github.com/buildfromzero/skill-map/common" + "github.com/buildfromzero/skill-map/managers" + "github.com/gin-gonic/gin" +) + +type SkillHandler struct { + groupName string + skillManager managers.SkillManager +} + +func NewSkillHandlerFrom(skillManager managers.SkillManager) *SkillHandler { + return &SkillHandler{ + "api/", + skillManager, + } +} + +func (handler *SkillHandler) RegisterEndpoints(r *gin.Engine) { + skillGroup := r.Group(handler.groupName) + // skill apis + skillGroup.GET("skills/", handler.ListSkills) + skillGroup.POST("skills/", handler.CreateSkill) + skillGroup.GET("skills/:skillid/", handler.SkillDetail) + skillGroup.DELETE("skills/:skillid/", handler.DeleteSkill) + skillGroup.PATCH("skills/:skillid/", handler.UpdateSkill) + // skill group apis + skillGroup.GET("skill-groups/", handler.ListSkillGroups) + skillGroup.POST("skill-groups/", handler.CreateSkillGroup) + skillGroup.GET("skill-groups/:groupid/", handler.SkillGroupDetail) + skillGroup.DELETE("skill-groups/:groupid/", handler.DeleteSkillGroup) + skillGroup.PATCH("skill-groups/:groupid/", handler.UpdateSkillGroup) +} + +func (handler *SkillHandler) CreateSkill(ctx *gin.Context) { + + inputData := common.NewSkillCreationInput() + + err := ctx.BindJSON(&inputData) + + if err != nil { + common.BadResponse(ctx, "Failed to bind data") + return + } + + newSkill, err := handler.skillManager.Create(inputData) + + if err != nil { + common.BadResponse(ctx, "failed to create skill") + return + } + + ctx.JSON(http.StatusOK, newSkill) +} + +func (handler *SkillHandler) ListSkills(ctx *gin.Context) { + + allSkills, err := handler.skillManager.List() + + if err != nil { + common.BadResponse(ctx, "failed to get all skills") + return + } + + ctx.JSON(http.StatusOK, allSkills) +} + +func (handler *SkillHandler) SkillDetail(ctx *gin.Context) { + + skillId, ok := ctx.Params.Get("skillid") + + if !ok { + fmt.Println("invalid skill id") + return + } + skill, err := handler.skillManager.Get(skillId) + + if err != nil { + common.BadResponse(ctx, "failed to get skill") + return + } + + ctx.JSON(http.StatusOK, skill) +} + +func (handler *SkillHandler) DeleteSkill(ctx *gin.Context) { + + skillId, ok := ctx.Params.Get("skillid") + + if !ok { + common.BadResponse(ctx, "invalid userid") + return + } + err := handler.skillManager.Delete(skillId) + + if err != nil { + common.BadResponse(ctx, err.Error()) + return + } + + common.SuccessResponse(ctx, "deleted Skill") +} + +func (handler *SkillHandler) UpdateSkill(ctx *gin.Context) { + + skillId, ok := ctx.Params.Get("skillid") + + if !ok { + common.BadResponse(ctx, "failed to delete skill") + return + } + + inputData := common.NewSkillUpdateInput() + + err := ctx.BindJSON(&inputData) + + if err != nil { + common.BadResponse(ctx, "failed to bind data") + return + } + + skill, err := handler.skillManager.Update(skillId, inputData) + + if err != nil { + common.BadResponse(ctx, "failed to update skill") + return + } + + ctx.JSON(http.StatusOK, skill) +} + +func (handler *SkillHandler) CreateSkillGroup(ctx *gin.Context) { + + inputData := common.NewSkillGroupCreationInput() + + err := ctx.BindJSON(&inputData) + + if err != nil { + common.BadResponse(ctx, "failed to bind data") + return + } + + newSkillGroup, err := handler.skillManager.CreateGroup(inputData) + + if err != nil { + common.BadResponse(ctx, "failed to create Skill Group") + return + } + + ctx.JSON(http.StatusOK, newSkillGroup) +} + +func (handler *SkillHandler) ListSkillGroups(ctx *gin.Context) { + + skillGroups, err := handler.skillManager.ListGroup() + + if err != nil { + common.BadResponse(ctx, "failed to get all Skill Groups") + return + } + + ctx.JSON(http.StatusOK, skillGroups) +} + +func (handler *SkillHandler) SkillGroupDetail(ctx *gin.Context) { + + groupId, ok := ctx.Params.Get("groupid") + + if !ok { + fmt.Println("invalid group id") + return + } + skillGroup, err := handler.skillManager.GetGroup(groupId) + + if err != nil { + common.BadResponse(ctx, "failed to get Skill Group") + return + } + + ctx.JSON(http.StatusOK, skillGroup) +} + +func (handler *SkillHandler) DeleteSkillGroup(ctx *gin.Context) { + + groupId, ok := ctx.Params.Get("groupid") + + if !ok { + common.BadResponse(ctx, "invalid Group id") + return + } + err := handler.skillManager.DeleteGroup(groupId) + + if err != nil { + common.BadResponse(ctx, err.Error()) + return + } + + common.SuccessResponse(ctx, "deleted Skill Group") +} + +func (handler *SkillHandler) UpdateSkillGroup(ctx *gin.Context) { + + groupId, ok := ctx.Params.Get("groupid") + + if !ok { + common.BadResponse(ctx, "failed to get group id") + return + } + + inputData := common.NewSkillGroupUpdateInput() + + err := ctx.BindJSON(&inputData) + + if err != nil { + common.BadResponse(ctx, err.Error()) + return + } + + skillGroup, err := handler.skillManager.UpdateGroup(groupId, inputData) + + if err != nil { + common.BadResponse(ctx, "failed to update Skill Group") + return + } + + ctx.JSON(http.StatusOK, skillGroup) +} diff --git a/handlers/user.go b/handlers/user.go index 287e6db..b0b9626 100644 --- a/handlers/user.go +++ b/handlers/user.go @@ -21,24 +21,24 @@ func NewUserHandlerFrom(userManager managers.UserManager) *UserHandler { } } -func (handler *UserHandler) RegisterUserApis(r *gin.Engine) { +func (handler *UserHandler) RegisterEndpoints(r *gin.Engine) { userGroup := r.Group(handler.groupName) - userGroup.GET("", handler.List) - userGroup.POST("", handler.Create) - userGroup.GET(":userid/", handler.Detail) - userGroup.DELETE(":userid/", handler.Delete) - userGroup.PATCH(":userid/", handler.Update) + userGroup.GET("", handler.ListUser) + userGroup.POST("", handler.CreateUser) + userGroup.GET(":userid/", handler.UserDetail) + userGroup.DELETE(":userid/", handler.DeleteUser) + userGroup.PATCH(":userid/", handler.UpdateUser) } -func (handler *UserHandler) Create(ctx *gin.Context) { +func (handler *UserHandler) CreateUser(ctx *gin.Context) { userData := common.NewUserCreationInput() err := ctx.BindJSON(&userData) if err != nil { - common.BadResponse(ctx, "Failed to bind data") + common.BadResponse(ctx, "failed to bind data") return } @@ -52,7 +52,7 @@ func (handler *UserHandler) Create(ctx *gin.Context) { ctx.JSON(http.StatusOK, newUser) } -func (handler *UserHandler) List(ctx *gin.Context) { +func (handler *UserHandler) ListUser(ctx *gin.Context) { allUsers, err := handler.userManager.List() @@ -64,49 +64,49 @@ func (handler *UserHandler) List(ctx *gin.Context) { ctx.JSON(http.StatusOK, allUsers) } -func (handler *UserHandler) Detail(ctx *gin.Context) { +func (handler *UserHandler) UserDetail(ctx *gin.Context) { userId, ok := ctx.Params.Get("userid") if !ok { fmt.Println("invalid userid") - } - user, err := handler.userManager.Get(userId) - - if user.ID == 0 { - common.BadResponse(ctx, "no user present") return } + user, err := handler.userManager.Get(userId) if err != nil { - common.BadResponse(ctx, "failed to get user") + common.BadResponse(ctx, err.Error()) + return } ctx.JSON(http.StatusOK, user) } -func (handler *UserHandler) Delete(ctx *gin.Context) { +func (handler *UserHandler) DeleteUser(ctx *gin.Context) { userId, ok := ctx.Params.Get("userid") if !ok { common.BadResponse(ctx, "invalid userid") + return } err := handler.userManager.Delete(userId) if err != nil { - common.BadResponse(ctx, "failed to delete user") + common.BadResponse(ctx, err.Error()) + return } common.SuccessResponse(ctx, "Deleted user") } -func (handler *UserHandler) Update(ctx *gin.Context) { +func (handler *UserHandler) UpdateUser(ctx *gin.Context) { userId, ok := ctx.Params.Get("userid") if !ok { common.BadResponse(ctx, "failed to delete user") + return } userData := common.NewUserUpdateInput() @@ -121,7 +121,7 @@ func (handler *UserHandler) Update(ctx *gin.Context) { user, err := handler.userManager.Update(userId, userData) if err != nil { - common.BadResponse(ctx, "failed to update user") + common.BadResponse(ctx, err.Error()) return } diff --git a/main.go b/main.go index 8ba9c0b..ea98089 100644 --- a/main.go +++ b/main.go @@ -15,15 +15,19 @@ func init() { } func main() { - fmt.Println("Skill Map") + fmt.Println("Stating Skill Map...") router := gin.Default() - router.Use(cors.Default()) userManger := managers.NewUserManager() + skillManger := managers.NewSkillManager() + userHandler := handlers.NewUserHandlerFrom(userManger) - userHandler.RegisterUserApis(router) + userHandler.RegisterEndpoints(router) + + skillHandler := handlers.NewSkillHandlerFrom(skillManger) + skillHandler.RegisterEndpoints(router) router.Run() // listen and serve on 0.0.0.0:8080 } diff --git a/managers/skill.go b/managers/skill.go new file mode 100644 index 0000000..86bb131 --- /dev/null +++ b/managers/skill.go @@ -0,0 +1,174 @@ +package managers + +import ( + "errors" + "fmt" + + "github.com/buildfromzero/skill-map/common" + "github.com/buildfromzero/skill-map/models" + "github.com/buildfromzero/skill-map/storage" +) + +type SkillManager interface { + // Skill + Create(inputData *common.SkillCreationInput) (*models.Skill, error) + List() ([]models.Skill, error) + Get(id string) (*models.Skill, error) + Update(id string, inputData *common.SkillUpdateInput) (*models.Skill, error) + Delete(id string) error + // Skill Group + CreateGroup(inputData *common.SkillGroupCreationInput) (*models.SkillGroup, error) + ListGroup() ([]models.SkillGroup, error) + GetGroup(id string) (*models.SkillGroup, error) + UpdateGroup(id string, inputData *common.SkillGroupUpdateInput) (*models.SkillGroup, error) + DeleteGroup(id string) error +} + +type skillManager struct { + // DatabaseDriver + // dbClient +} + +func NewSkillManager() SkillManager { + return &skillManager{} +} + +func (skillMgr *skillManager) Create(inputData *common.SkillCreationInput) (*models.Skill, error) { + newSkillObj := &models.Skill{Name: inputData.Name} + storage.DB.Create(newSkillObj) + + if newSkillObj.ID == 0 { + return nil, errors.New("skill creation failed") + } + + return newSkillObj, nil +} + +func (skillMgr *skillManager) List() ([]models.Skill, error) { + skillObj := []models.Skill{} + + storage.DB.Find(&skillObj) + // TODO: handle errors + return skillObj, nil +} + +func (skillMgr *skillManager) Get(id string) (*models.Skill, error) { + skillObj := models.NewSkill() + + storage.DB.First(skillObj, id) + + if skillObj.ID == 0 { + return nil, errors.New("no skill found") + } + + return skillObj, nil +} + +func (skillMgr *skillManager) Update(id string, inputData *common.SkillUpdateInput) (*models.Skill, error) { + skillObj := &models.Skill{} + + storage.DB.First(skillObj, id) + + if skillObj.ID == 0 { + return nil, errors.New("item does not exist") + } + + storage.DB.Model(skillObj).Updates(models.Skill{Name: inputData.Name}) + // TODO: handle errors + return skillObj, nil +} + +func (skillMgr *skillManager) Delete(id string) error { + skillObj := models.Skill{} + + storage.DB.First(&skillObj, id) + if skillObj.ID == 0 { + return errors.New("item does not exist") + } + storage.DB.Delete(&skillObj) + // TODO: handle errors + return nil +} + +func (skillMgr *skillManager) CreateGroup(inputData *common.SkillGroupCreationInput) (*models.SkillGroup, error) { + newSkillGroupObj := &models.SkillGroup{Name: inputData.Name} + storage.DB.Create(newSkillGroupObj) + + if newSkillGroupObj.ID == 0 { + return nil, errors.New("skill group creation failed") + } + + return newSkillGroupObj, nil +} + +func (skillMgr *skillManager) ListGroup() ([]models.SkillGroup, error) { + skillGroups := []models.SkillGroup{} + + // storage.DB.Find(&skillGroupObj) + storage.DB.Model(&skillGroups).Preload("Skills").Find(&skillGroups) + // TODO: handle errors + return skillGroups, nil +} + +func (skillMgr *skillManager) GetGroup(id string) (*models.SkillGroup, error) { + skillGroupObj := models.NewSkillGroup() + + storage.DB.Model(skillGroupObj).Preload("Skills").Find(skillGroupObj) + + if skillGroupObj.ID == 0 { + return nil, errors.New("item does not exist") + } + + return skillGroupObj, nil +} + +func (skillMgr *skillManager) UpdateGroup(id string, inputData *common.SkillGroupUpdateInput) (*models.SkillGroup, error) { + + skillGroupObj := models.NewSkillGroup() + + storage.DB.First(skillGroupObj, id) + + if skillGroupObj.ID == 0 { + return nil, errors.New("item does not exist") + } + + skillMapping, err := skillMgr.getSkillList(inputData.Skills) + + if err != nil { + return nil, err + } + + storage.DB.Model(skillGroupObj).Updates(models.Skill{Name: inputData.Name}) + storage.DB.Model(skillGroupObj).Association("Skills").Replace(skillMapping) + // TODO: handle errors + return skillGroupObj, nil +} + +func (skillMgr *skillManager) getSkillList(inputList []int) ([]*models.Skill, error) { + + skills := []*models.Skill{} + + for _, id := range inputList { + skill := models.NewSkill() + storage.DB.First(skill, id) + if skill.ID == 0 { + return nil, fmt.Errorf("skill with id %v not exists", id) + } + skills = append(skills, skill) + } + + return skills, nil +} + +func (skillMgr *skillManager) DeleteGroup(id string) error { + skillGroupObj := models.NewSkillGroup() + + storage.DB.First(skillGroupObj, id) + + if skillGroupObj.ID == 0 { + return errors.New("item does not exist") + } + storage.DB.Delete(skillGroupObj) + // TODO: handle errors + return nil +} diff --git a/managers/user.go b/managers/user.go index 1a0a34a..6f38496 100644 --- a/managers/user.go +++ b/managers/user.go @@ -11,7 +11,7 @@ import ( type UserManager interface { Create(userData *common.UserCreationInput) (*models.User, error) List() ([]models.User, error) - Get(id string) (models.User, error) + Get(id string) (*models.User, error) Update(userId string, userData *common.UserUpdateInput) (*models.User, error) Delete(id string) error } @@ -42,10 +42,13 @@ func (userMgr *userManager) List() ([]models.User, error) { return users, nil } -func (userMgr *userManager) Get(id string) (models.User, error) { - user := models.User{} +func (userMgr *userManager) Get(id string) (*models.User, error) { + user := &models.User{} storage.DB.First(&user, id) + if user.ID == 0 { + return nil, errors.New("no user found") + } return user, nil } @@ -55,6 +58,11 @@ func (userMgr *userManager) Update(userId string, userData *common.UserUpdateInp user := models.User{} storage.DB.First(&user, userId) + + if user.ID == 0 { + return nil, errors.New("no user found") + } + storage.DB.Model(&user).Updates(models.User{FullName: userData.FullName, Email: userData.Email}) return &user, nil @@ -64,6 +72,11 @@ func (userMgr *userManager) Delete(id string) error { user := models.User{} storage.DB.First(&user, id) + + if user.ID == 0 { + return errors.New("no user found") + } + storage.DB.Delete(&user) return nil } diff --git a/models/ranking.go b/models/ranking.go new file mode 100644 index 0000000..a1cedf2 --- /dev/null +++ b/models/ranking.go @@ -0,0 +1,19 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type UserSkillRank struct { + ID uint `gorm:"primarykey" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + UserID int + User User + SkillID int + Skill Skill + Rank uint `json:"rank"` +} diff --git a/models/skill.go b/models/skill.go new file mode 100644 index 0000000..a0506b8 --- /dev/null +++ b/models/skill.go @@ -0,0 +1,32 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type Skill struct { + ID uint `gorm:"primarykey" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + Name string `json:"name"` +} + +type SkillGroup struct { + ID uint `gorm:"primarykey" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + Name string `json:"name"` + Skills []Skill `gorm:"many2many:skillgroup_skills;" json:"skills"` +} + +func NewSkill() *Skill { + return &Skill{} +} + +func NewSkillGroup() *SkillGroup { + return &SkillGroup{} +} diff --git a/storage/db.go b/storage/db.go index bb63b7b..995238f 100644 --- a/storage/db.go +++ b/storage/db.go @@ -15,5 +15,10 @@ func InitializeDatabase() { panic("failed to connect database") } - DB.AutoMigrate(&models.User{}) + DB.AutoMigrate( + &models.User{}, + &models.Skill{}, + &models.SkillGroup{}, + &models.UserSkillRank{}, + ) }