77 "log/slog"
88 "math/big"
99 "net/http"
10- "strconv"
1110
1211 "windshift/internal/logger"
1312 "windshift/internal/models"
@@ -28,13 +27,12 @@ type UserHandler struct {
2827 offboardUser func (id int ) error
2928 deactivateCascade func (id int ) (services.AgentDeactivationResult , error )
3029 invalidateSessions func (id int )
31- agentPresence * services.AgentPresenceService // optional; nil when the agent harness is off
30+ workspaceUsers * services.WorkspaceUserResolver
3231}
3332
34- // SetAgentPresenceService wires the optional presence resolver used to
35- // decorate agent users in the assignable-users response (WI-272).
36- func (h * UserHandler ) SetAgentPresenceService (s * services.AgentPresenceService ) {
37- h .agentPresence = s
33+ // SetWorkspaceUserResolver wires the shared picker and validation roster.
34+ func (h * UserHandler ) SetWorkspaceUserResolver (resolver * services.WorkspaceUserResolver ) {
35+ h .workspaceUsers = resolver
3836}
3937
4038func (h * UserHandler ) invalidateUserSessions (userID int ) {
@@ -770,44 +768,34 @@ func (h *UserHandler) ResetPassword(w http.ResponseWriter, r *http.Request) {
770768 respondJSONOK (w , response )
771769}
772770
773- // GetAssignable returns only active users with limited fields for assignment
774- // pickers. Agent users are decorated with a workspace-scoped presence signal
775- // (online/offline/local/unbound, WI-272) so assigners can see whether
776- // assigning to the agent would actually start a run.
771+ // GetAssignable returns the shared mention and assignment roster. Every user
772+ // can view the workspace; agents also have a ready binding there.
777773func (h * UserHandler ) GetAssignable (w http.ResponseWriter , r * http.Request ) {
778- if _ , ok := RequireAuth (w , r ); ! ok {
774+ currentUser , ok := RequireAuth (w , r )
775+ if ! ok {
779776 return
780777 }
781-
782- users , err := h .userSvc .ListAll ()
778+ workspaceID , ok := requireIDParam (w , r , "workspaceId" )
779+ if ! ok {
780+ return
781+ }
782+ canView , err := h .permissionService .HasWorkspacePermission (currentUser .ID , workspaceID , models .PermissionItemView )
783783 if err != nil {
784784 respondInternalError (w , r , err )
785785 return
786786 }
787-
788- // Presence enrichment is best-effort: a resolver failure degrades to
789- // undecorated users, never a failed picker.
790- var presence map [int ]string
791- if h .agentPresence != nil {
792- if workspaceID , err := strconv .Atoi (r .PathValue ("workspaceId" )); err == nil && workspaceID > 0 {
793- if presence , err = h .agentPresence .ForWorkspace (r .Context (), workspaceID ); err != nil {
794- slog .Warn ("assignable users: resolve agent presence" , "workspace_id" , workspaceID , "error" , err )
795- presence = nil
796- }
797- }
787+ if ! canView {
788+ respondNotFound (w , r , "workspace" )
789+ return
798790 }
799-
800- for i := range users {
801- users [i ].Email = ""
802- users [i ].Timezone = ""
803- users [i ].Language = ""
804- if users [i ].IsAgent && presence != nil {
805- if p , ok := presence [users [i ].ID ]; ok {
806- users [i ].AgentPresence = p
807- } else {
808- users [i ].AgentPresence = services .AgentPresenceUnbound
809- }
810- }
791+ if h .workspaceUsers == nil {
792+ respondInternalError (w , r , errors .New ("workspace user resolver is not configured" ))
793+ return
794+ }
795+ users , err := h .workspaceUsers .List (r .Context (), workspaceID )
796+ if err != nil {
797+ respondInternalError (w , r , err )
798+ return
811799 }
812800
813801 respondJSONOK (w , users )
0 commit comments