Changeset 0.8.1 (#44)

This commit is contained in:
2026-02-22 01:22:23 +00:00
parent 1adef94617
commit c0d95fd7f5
13 changed files with 473 additions and 55 deletions

View File

@@ -17,15 +17,22 @@ import (
// ── Types ───────────────────────────────────
type adminUserResponse struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
DisplayName *string `json:"display_name"`
Role string `json:"role"`
IsActive bool `json:"is_active"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
LastLoginAt *string `json:"last_login_at"`
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
DisplayName *string `json:"display_name"`
Role string `json:"role"`
IsActive bool `json:"is_active"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
LastLoginAt *string `json:"last_login_at"`
Teams []userTeamMembership `json:"teams,omitempty"`
}
type userTeamMembership struct {
TeamID string `json:"team_id"`
TeamName string `json:"team_name"`
Role string `json:"role"`
}
type adminCreateUserRequest struct {
@@ -44,7 +51,9 @@ type updateUserRoleRequest struct {
}
type updateUserActiveRequest struct {
IsActive bool `json:"is_active"`
IsActive bool `json:"is_active"`
TeamIDs []string `json:"team_ids,omitempty"` // assign to teams on activation
TeamRole string `json:"team_role,omitempty"` // role for team assignment (default: member)
}
type globalSettingResponse struct {
@@ -98,6 +107,38 @@ func (h *AdminHandler) ListUsers(c *gin.Context) {
users = append(users, u)
}
// Batch-load team memberships for all users
if len(users) > 0 {
userIDs := make([]interface{}, len(users))
placeholders := make([]string, len(users))
userIdx := make(map[string]int) // user_id → index in users slice
for i, u := range users {
userIDs[i] = u.ID
placeholders[i] = "$" + strconv.Itoa(i+1)
userIdx[u.ID] = i
}
tmRows, err := database.DB.Query(`
SELECT tm.user_id, t.id, t.name, tm.role
FROM team_members tm
JOIN teams t ON t.id = tm.team_id
WHERE tm.user_id IN (`+strings.Join(placeholders, ",")+`)
ORDER BY t.name ASC
`, userIDs...)
if err == nil {
defer tmRows.Close()
for tmRows.Next() {
var uid, tid, tname, trole string
if tmRows.Scan(&uid, &tid, &tname, &trole) == nil {
if idx, ok := userIdx[uid]; ok {
users[idx].Teams = append(users[idx].Teams, userTeamMembership{
TeamID: tid, TeamName: tname, Role: trole,
})
}
}
}
}
}
c.JSON(http.StatusOK, paginatedResponse{
Data: users,
Page: page,
@@ -251,7 +292,30 @@ func (h *AdminHandler) ToggleUserActive(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "user updated", "is_active": req.IsActive})
// On activation, optionally assign to teams
teamsAssigned := 0
if req.IsActive && len(req.TeamIDs) > 0 {
role := req.TeamRole
if role == "" {
role = "member"
}
for _, teamID := range req.TeamIDs {
_, err := database.DB.Exec(`
INSERT INTO team_members (team_id, user_id, role)
VALUES ($1, $2, $3)
ON CONFLICT (team_id, user_id) DO NOTHING
`, teamID, targetID, role)
if err == nil {
teamsAssigned++
}
}
}
c.JSON(http.StatusOK, gin.H{
"message": "user updated",
"is_active": req.IsActive,
"teams_assigned": teamsAssigned,
})
}
// ── Delete User ─────────────────────────────