diff --git a/controller/router/router.go b/controller/router/router.go index 585c4dc..3827929 100644 --- a/controller/router/router.go +++ b/controller/router/router.go @@ -98,6 +98,17 @@ func SetupRoutes() { botGroup.Get("/status", middleware.AuthNeeded(), service.GetWSStatus) botGroup.Delete("/ws/kick/:session_id", middleware.AuthNeeded(), service.KickConnection) + departmentGroup := (*router).Group("/department") + departmentGroup.Get("/", service.GetDepartmentList) + departmentGroup.Post("/create", middleware.AuthNeeded(), service.CreateDepartment) + departmentGroup.Patch("/", middleware.AuthNeeded(), service.UpdateDepartment) + departmentGroup.Patch("/order", middleware.AuthNeeded(), service.UpdateDepartmentOrder) + departmentGroup.Delete("/:id", middleware.AuthNeeded(), service.DeleteDepartment) + departmentGroup.Post("/:id/member", middleware.AuthNeeded(), service.AddDepartmentMember) + departmentGroup.Delete("/:id/member/:username", middleware.AuthNeeded(), service.RemoveDepartmentMember) + departmentGroup.Patch("/:id/member/:username/leader", middleware.AuthNeeded(), service.UpdateDepartmentMemberLeaderStatus) + departmentGroup.Patch("/:id/member/order", middleware.AuthNeeded(), service.UpdateDepartmentMemberOrder) + wikiGroup := (*router).Group("/wiki") wikiGroup.Get("/glossary", service.GetGlossaryList) wikiGroup.Get("/glossary/:id", service.GetGlossaryById) diff --git a/dao/department.go b/dao/department.go new file mode 100644 index 0000000..96f8016 --- /dev/null +++ b/dao/department.go @@ -0,0 +1,146 @@ +package dao + +import ( + "fmt" + "necore/database" + "necore/model" + + "gorm.io/gorm" +) + +func GetDepartmentList() ([]model.Department, error) { + db := database.GetDepartmentDatabase() + var departments []model.Department + err := db.Order("sort_order asc, id asc").Find(&departments).Error + return departments, err +} + +func GetDepartmentByID(id string) (*model.Department, error) { + db := database.GetDepartmentDatabase() + var department model.Department + if err := db.Where("id = ?", id).First(&department).Error; err != nil { + return nil, err + } + return &department, nil +} + +func CreateDepartment(department model.Department) error { + return database.GetDepartmentDatabase().Create(&department).Error +} + +func UpdateDepartment(department model.Department) error { + result := database.GetDepartmentDatabase(). + Model(&model.Department{}). + Where("id = ?", department.Id). + Updates(map[string]any{ + "name": department.Name, + "description": department.Description, + "icon": department.Icon, + "sort_order": department.SortOrder, + }) + + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return fmt.Errorf("department not found") + } + return nil +} + +func UpdateDepartmentOrders(orders []model.Department) error { + db := database.GetDepartmentDatabase() + return db.Transaction(func(tx *gorm.DB) error { + for _, department := range orders { + result := tx.Model(&model.Department{}). + Where("id = ?", department.Id). + Update("sort_order", department.SortOrder) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return fmt.Errorf("department not found") + } + } + return nil + }) +} + +func DeleteDepartment(id string) error { + db := database.GetDepartmentDatabase() + return db.Transaction(func(tx *gorm.DB) error { + if err := tx.Unscoped().Where("department_id = ?", id).Delete(&model.DepartmentMember{}).Error; err != nil { + return err + } + result := tx.Unscoped().Where("id = ?", id).Delete(&model.Department{}) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return fmt.Errorf("department not found") + } + return nil + }) +} + +func GetDepartmentMembers(departmentID string) ([]model.DepartmentMember, error) { + db := database.GetDepartmentDatabase() + var members []model.DepartmentMember + err := db.Where("department_id = ?", departmentID). + Order("sort_order asc, username asc"). + Find(&members).Error + return members, err +} + +func AddDepartmentMember(member model.DepartmentMember) error { + return database.GetDepartmentDatabase().Create(&member).Error +} + +func RemoveDepartmentMember(departmentID, username string) error { + result := database.GetDepartmentDatabase(). + Unscoped(). + Where("department_id = ? AND username = ?", departmentID, username). + Delete(&model.DepartmentMember{}) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return fmt.Errorf("department member not found") + } + return nil +} + +func UpdateDepartmentMemberOrders(departmentID string, members []model.DepartmentMember) error { + db := database.GetDepartmentDatabase() + return db.Transaction(func(tx *gorm.DB) error { + for _, member := range members { + result := tx.Model(&model.DepartmentMember{}). + Where("department_id = ? AND username = ?", departmentID, member.Username). + Updates(map[string]any{ + "sort_order": member.SortOrder, + "is_leader": member.IsLeader, + }) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return fmt.Errorf("department member not found") + } + } + return nil + }) +} + +func UpdateDepartmentMemberLeader(departmentID, username string, isLeader bool) error { + result := database.GetDepartmentDatabase(). + Model(&model.DepartmentMember{}). + Where("department_id = ? AND username = ?", departmentID, username). + Update("is_leader", isLeader) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return fmt.Errorf("department member not found") + } + return nil +} diff --git a/database/database.go b/database/database.go index 43dbbb5..97188fe 100644 --- a/database/database.go +++ b/database/database.go @@ -19,6 +19,8 @@ var documentDatabase *gorm.DB var botTokenDatabase *gorm.DB +var departmentDatabase *gorm.DB + var wikiDatabase *gorm.DB func ConnectSqlite() { @@ -58,6 +60,12 @@ func ConnectSqlite() { panic("failed to connect wiki database") } wikiDatabase.AutoMigrate(&model.Glossary{}, &model.Item{}) + + departmentDatabase, err = gorm.Open(sqlite.Open("data/department.sqlite3"), &gorm.Config{}) + if err != nil { + panic("failed to connect department database") + } + departmentDatabase.AutoMigrate(&model.Department{}, &model.DepartmentMember{}) } func GetUserDatabase() *gorm.DB { @@ -80,6 +88,10 @@ func GetBotTokenDatabase() *gorm.DB { return botTokenDatabase } +func GetDepartmentDatabase() *gorm.DB { + return departmentDatabase +} + func GetWikiDatabase() *gorm.DB { return wikiDatabase } diff --git a/model/department.go b/model/department.go new file mode 100644 index 0000000..6849053 --- /dev/null +++ b/model/department.go @@ -0,0 +1,13 @@ +package model + +import "gorm.io/gorm" + +type Department struct { + gorm.Model + + Id string `gorm:"uniqueIndex;not null" json:"id"` + Name string `gorm:"not null" json:"name"` + Description string `json:"description"` + Icon string `json:"icon"` + SortOrder int `gorm:"not null;default:0" json:"sortOrder"` +} diff --git a/model/department_member.go b/model/department_member.go new file mode 100644 index 0000000..fc615c6 --- /dev/null +++ b/model/department_member.go @@ -0,0 +1,12 @@ +package model + +import "gorm.io/gorm" + +type DepartmentMember struct { + gorm.Model + + DepartmentId string `gorm:"index;not null;uniqueIndex:idx_dept_user" json:"departmentId"` + Username string `gorm:"index;not null;uniqueIndex:idx_dept_user" json:"username"` + SortOrder int `gorm:"not null;default:0" json:"sortOrder"` + IsLeader bool `gorm:"not null;default:false" json:"isLeader"` +} diff --git a/routes_and_security_test.go b/routes_and_security_test.go index b57e869..6084e06 100644 --- a/routes_and_security_test.go +++ b/routes_and_security_test.go @@ -68,6 +68,7 @@ func setupTestEnv(t *testing.T) *testEnv { setGormLoggerSilent(database.GetServerDatabase()) setGormLoggerSilent(database.GetDocumentDatabase()) setGormLoggerSilent(database.GetBotTokenDatabase()) + setGormLoggerSilent(database.GetDepartmentDatabase()) // 必须在 Windows 删除 TempDir 前关闭 SQLite 连接池,否则数据库文件会被锁定。 t.Cleanup(func() { @@ -76,6 +77,7 @@ func setupTestEnv(t *testing.T) *testEnv { closeGormDB(t, database.GetServerDatabase()) closeGormDB(t, database.GetDocumentDatabase()) closeGormDB(t, database.GetBotTokenDatabase()) + closeGormDB(t, database.GetDepartmentDatabase()) }) must(t, dao.AddUserByUsername("admin", "admin-pass")) @@ -281,6 +283,17 @@ func registerRoutes(app *fiber.App) { botGroup.Delete("/token/:id", middleware.AuthNeeded(), service.DeleteBotToken) botGroup.Get("/status", middleware.AuthNeeded(), service.GetWSStatus) botGroup.Delete("/ws/kick/:session_id", middleware.AuthNeeded(), service.KickConnection) + + departmentGroup := api.Group("/department") + departmentGroup.Get("/", service.GetDepartmentList) + departmentGroup.Post("/create", middleware.AuthNeeded(), service.CreateDepartment) + departmentGroup.Patch("/", middleware.AuthNeeded(), service.UpdateDepartment) + departmentGroup.Patch("/order", middleware.AuthNeeded(), service.UpdateDepartmentOrder) + departmentGroup.Delete("/:id", middleware.AuthNeeded(), service.DeleteDepartment) + departmentGroup.Post("/:id/member", middleware.AuthNeeded(), service.AddDepartmentMember) + departmentGroup.Delete("/:id/member/:username", middleware.AuthNeeded(), service.RemoveDepartmentMember) + departmentGroup.Patch("/:id/member/:username/leader", middleware.AuthNeeded(), service.UpdateDepartmentMemberLeaderStatus) + departmentGroup.Patch("/:id/member/order", middleware.AuthNeeded(), service.UpdateDepartmentMemberOrder) } func must(t *testing.T, err error) { @@ -945,6 +958,81 @@ func TestDocument_PathTraversalUpload(t *testing.T) { } } +/* +DEPARTMENT ROUTES +*/ +func TestDepartmentRoutes(t *testing.T) { + env := setupTestEnv(t) + + assertStatus(t, doJSON(t, env, http.MethodGet, "/necore/department/", "", nil), http.StatusOK) + + createResp := doJSON(t, env, http.MethodPost, "/necore/department/create", env.adminToken, fiber.Map{ + "name": "运维保障部", + "description": "负责服务器与网站稳定运行", + "icon": "/contents/dept/icon.png", + "sortOrder": 1, + }) + assertStatus(t, createResp, http.StatusOK) + createBody := decodeBody(t, createResp) + deptID, _ := createBody["id"].(string) + if deptID == "" { + t.Fatalf("create department should return id, got %#v", createBody) + } + + assertStatus(t, doJSON(t, env, http.MethodPost, "/necore/department/"+deptID+"/member", env.adminToken, fiber.Map{ + "username": "alice", + "sortOrder": 1, + "isLeader": true, + }), http.StatusOK) + + listResp := doJSON(t, env, http.MethodGet, "/necore/department/", "", nil) + assertStatus(t, listResp, http.StatusOK) + listBody := decodeBody(t, listResp) + departments, ok := listBody["departments"].([]any) + if !ok || len(departments) != 1 { + t.Fatalf("department list = %#v", listBody["departments"]) + } + dept := departments[0].(map[string]any) + members, ok := dept["members"].([]any) + if !ok || len(members) != 1 { + t.Fatalf("members = %#v", dept["members"]) + } + member := members[0].(map[string]any) + if member["isLeader"] != true { + t.Fatalf("expected isLeader true after add, got %#v (%T)", member["isLeader"], member["isLeader"]) + } + + assertStatus(t, doJSON(t, env, http.MethodPatch, "/necore/department/"+deptID+"/member/alice/leader", env.adminToken, fiber.Map{ + "isLeader": false, + }), http.StatusOK) + + listAfterToggleResp := doJSON(t, env, http.MethodGet, "/necore/department/", "", nil) + assertStatus(t, listAfterToggleResp, http.StatusOK) + listAfterToggleBody := decodeBody(t, listAfterToggleResp) + departmentsAfterToggle, ok := listAfterToggleBody["departments"].([]any) + if !ok || len(departmentsAfterToggle) != 1 { + t.Fatalf("department list after toggle = %#v", listAfterToggleBody["departments"]) + } + deptAfterToggle := departmentsAfterToggle[0].(map[string]any) + membersAfterToggle, ok := deptAfterToggle["members"].([]any) + if !ok || len(membersAfterToggle) != 1 { + t.Fatalf("members after toggle = %#v", deptAfterToggle["members"]) + } + memberAfterToggle := membersAfterToggle[0].(map[string]any) + if memberAfterToggle["isLeader"] != false { + t.Fatalf("expected isLeader false after toggle, got %#v (%T)", memberAfterToggle["isLeader"], memberAfterToggle["isLeader"]) + } + + assertStatus(t, doJSON(t, env, http.MethodPatch, "/necore/department/order", env.adminToken, fiber.Map{ + "orders": []fiber.Map{ + {"id": deptID, "sortOrder": 2}, + }, + }), http.StatusOK) + + assertStatus(t, doJSON(t, env, http.MethodDelete, "/necore/department/"+deptID+"/member/alice", env.adminToken, nil), http.StatusOK) + assertStatus(t, doJSON(t, env, http.MethodDelete, "/necore/department/"+deptID, env.adminToken, nil), http.StatusOK) +} + /* 5. WEBSOCKET SECURITY TEST */ diff --git a/service/department.go b/service/department.go new file mode 100644 index 0000000..1a77bb0 --- /dev/null +++ b/service/department.go @@ -0,0 +1,436 @@ +package service + +import ( + "encoding/json" + "net/url" + "sort" + "strings" + + "necore/dao" + "necore/model" + + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" +) + +type departmentTagEntity struct { + Text string `json:"text"` + Color string `json:"color"` + TagColor string `json:"tagColor"` +} + +type departmentMemberEntity struct { + Username string `json:"username"` + Avatar string `json:"avatar,omitempty"` + Group []string `json:"group"` + Tags []departmentTagEntity `json:"tags"` + IsLeader bool `json:"isLeader"` +} + +type departmentMemberSortable struct { + Member departmentMemberEntity + SortOrder int +} + +func checkDepartmentPermission(c *fiber.Ctx) bool { + user := c.Locals("currentUser").(model.User) + isAdmin := dao.ContainsGroup(user.Group, "admin") + if isAdmin { + return false + } + return true +} + +func parseDepartmentMember(user model.User, isLeader bool) departmentMemberEntity { + var groups []string + if err := json.Unmarshal([]byte(user.Group), &groups); err != nil { + groups = []string{} + } + var tags []departmentTagEntity + if err := json.Unmarshal([]byte(user.Tags), &tags); err != nil { + tags = []departmentTagEntity{} + } + return departmentMemberEntity{ + Username: user.Username, + Avatar: user.Avatar, + Group: groups, + Tags: tags, + IsLeader: isLeader, + } +} + +func buildDepartmentMembers(departmentID string) ([]departmentMemberEntity, error) { + memberships, err := dao.GetDepartmentMembers(departmentID) + if err != nil { + return nil, err + } + + sortable := make([]departmentMemberSortable, 0, len(memberships)) + for _, membership := range memberships { + user, err := dao.GetUserByUsername(membership.Username) + if err != nil { + return nil, err + } + if user == nil { + continue + } + sortable = append(sortable, departmentMemberSortable{ + Member: parseDepartmentMember(*user, membership.IsLeader), + SortOrder: membership.SortOrder, + }) + } + + sort.SliceStable(sortable, func(i, j int) bool { + if sortable[i].Member.IsLeader != sortable[j].Member.IsLeader { + return sortable[i].Member.IsLeader + } + if sortable[i].SortOrder != sortable[j].SortOrder { + return sortable[i].SortOrder < sortable[j].SortOrder + } + return sortable[i].Member.Username < sortable[j].Member.Username + }) + + result := make([]departmentMemberEntity, len(sortable)) + for i, item := range sortable { + result[i] = item.Member + } + return result, nil +} + +func GetDepartmentList(c *fiber.Ctx) error { + departments, err := dao.GetDepartmentList() + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + + type Response struct { + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Icon string `json:"icon"` + SortOrder int `json:"sortOrder"` + Members []departmentMemberEntity `json:"members"` + } + + res := make([]Response, 0, len(departments)) + for _, department := range departments { + members, err := buildDepartmentMembers(department.Id) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + res = append(res, Response{ + Id: department.Id, + Name: department.Name, + Description: department.Description, + Icon: department.Icon, + SortOrder: department.SortOrder, + Members: members, + }) + } + + return c.JSON(fiber.Map{ + "departments": res, + }) +} + +func CreateDepartment(c *fiber.Ctx) error { + if checkDepartmentPermission(c) { + return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ + "error": "Forbidden", + }) + } + + type Payload struct { + Name string `json:"name"` + Description string `json:"description"` + Icon string `json:"icon"` + SortOrder int `json:"sortOrder"` + } + + payload := new(Payload) + if err := c.BodyParser(payload); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": err.Error(), + }) + } + if strings.TrimSpace(payload.Name) == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Department name is required", + }) + } + + id := uuid.New().String() + department := model.Department{ + Id: id, + Name: payload.Name, + Description: payload.Description, + Icon: payload.Icon, + SortOrder: payload.SortOrder, + } + if err := dao.CreateDepartment(department); err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + + return c.JSON(fiber.Map{ + "id": id, + }) +} + +func UpdateDepartment(c *fiber.Ctx) error { + if checkDepartmentPermission(c) { + return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ + "error": "Forbidden", + }) + } + + var department model.Department + if err := c.BodyParser(&department); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": err.Error(), + }) + } + if strings.TrimSpace(department.Id) == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Department id is required", + }) + } + if strings.TrimSpace(department.Name) == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Department name is required", + }) + } + + if err := dao.UpdateDepartment(department); err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + return c.SendStatus(fiber.StatusOK) +} + +func UpdateDepartmentOrder(c *fiber.Ctx) error { + if checkDepartmentPermission(c) { + return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ + "error": "Forbidden", + }) + } + + type OrderItem struct { + Id string `json:"id"` + SortOrder int `json:"sortOrder"` + } + type Payload struct { + Orders []OrderItem `json:"orders"` + } + + payload := new(Payload) + if err := c.BodyParser(payload); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": err.Error(), + }) + } + if len(payload.Orders) == 0 { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Orders are required", + }) + } + + orders := make([]model.Department, len(payload.Orders)) + for i, item := range payload.Orders { + orders[i] = model.Department{ + Id: item.Id, + SortOrder: item.SortOrder, + } + } + + if err := dao.UpdateDepartmentOrders(orders); err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + return c.SendStatus(fiber.StatusOK) +} + +func DeleteDepartment(c *fiber.Ctx) error { + if checkDepartmentPermission(c) { + return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ + "error": "Forbidden", + }) + } + + if err := dao.DeleteDepartment(c.Params("id")); err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + return c.SendStatus(fiber.StatusOK) +} + +func AddDepartmentMember(c *fiber.Ctx) error { + if checkDepartmentPermission(c) { + return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ + "error": "Forbidden", + }) + } + + departmentID := c.Params("id") + type Payload struct { + Username string `json:"username"` + SortOrder int `json:"sortOrder"` + IsLeader bool `json:"isLeader"` + } + payload := new(Payload) + if err := c.BodyParser(payload); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": err.Error(), + }) + } + if strings.TrimSpace(payload.Username) == "" { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Username is required", + }) + } + + if _, err := dao.GetDepartmentByID(departmentID); err != nil { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ + "error": "Department not found", + }) + } + + user, err := dao.GetUserByUsername(payload.Username) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + if user == nil { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ + "error": "User not found", + }) + } + + member := model.DepartmentMember{ + DepartmentId: departmentID, + Username: payload.Username, + SortOrder: payload.SortOrder, + IsLeader: payload.IsLeader, + } + if err := dao.AddDepartmentMember(member); err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + return c.SendStatus(fiber.StatusOK) +} + +func RemoveDepartmentMember(c *fiber.Ctx) error { + if checkDepartmentPermission(c) { + return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ + "error": "Forbidden", + }) + } + + username, err := url.PathUnescape(c.Params("username")) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Invalid username", + }) + } + + if err := dao.RemoveDepartmentMember(c.Params("id"), username); err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + return c.SendStatus(fiber.StatusOK) +} + +func UpdateDepartmentMemberOrder(c *fiber.Ctx) error { + if checkDepartmentPermission(c) { + return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ + "error": "Forbidden", + }) + } + + departmentID := c.Params("id") + type MemberOrder struct { + Username string `json:"username"` + SortOrder int `json:"sortOrder"` + IsLeader bool `json:"isLeader"` + } + type Payload struct { + Members []MemberOrder `json:"members"` + } + + payload := new(Payload) + if err := c.BodyParser(payload); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": err.Error(), + }) + } + if len(payload.Members) == 0 { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Members are required", + }) + } + + members := make([]model.DepartmentMember, len(payload.Members)) + for i, item := range payload.Members { + members[i] = model.DepartmentMember{ + Username: item.Username, + SortOrder: item.SortOrder, + IsLeader: item.IsLeader, + } + } + + if err := dao.UpdateDepartmentMemberOrders(departmentID, members); err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + return c.SendStatus(fiber.StatusOK) +} + +func UpdateDepartmentMemberLeaderStatus(c *fiber.Ctx) error { + if checkDepartmentPermission(c) { + return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ + "error": "Forbidden", + }) + } + + departmentID := c.Params("id") + username, err := url.PathUnescape(c.Params("username")) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Invalid username", + }) + } + + type Payload struct { + IsLeader bool `json:"isLeader"` + } + payload := new(Payload) + if err := c.BodyParser(payload); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": err.Error(), + }) + } + + if err := dao.UpdateDepartmentMemberLeader(departmentID, username, payload.IsLeader); err != nil { + if strings.Contains(err.Error(), "not found") { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ + "error": err.Error(), + }) + } + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + return c.SendStatus(fiber.StatusOK) +}