feat: accept usernames or UUIDs in follow management

This commit is contained in:
Patrick Britton 2026-02-09 10:05:46 -06:00
parent a4909723d9
commit fa0cca9b34
2 changed files with 27 additions and 6 deletions

View file

@ -512,7 +512,7 @@ function FollowManager({ userId }: { userId: string }) {
{/* Add */} {/* Add */}
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<input type="text" placeholder="User ID to add" value={addHandle} <input type="text" placeholder="Username or user ID" value={addHandle}
onChange={(e) => setAddHandle(e.target.value)} onChange={(e) => setAddHandle(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleAdd()} onKeyDown={(e) => e.key === 'Enter' && handleAdd()}
className="flex-1 px-2 py-1.5 border border-warm-300 rounded text-sm" /> className="flex-1 px-2 py-1.5 border border-warm-300 rounded text-sm" />

View file

@ -689,6 +689,22 @@ func (h *AdminHandler) AdminUpdateProfile(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Profile updated"}) c.JSON(http.StatusOK, gin.H{"message": "Profile updated"})
} }
// resolveUserID accepts either a UUID or a handle and returns the user's UUID.
func (h *AdminHandler) resolveUserID(ctx context.Context, input string) (string, error) {
// If it parses as a UUID, return as-is
if _, err := uuid.Parse(input); err == nil {
return input, nil
}
// Otherwise, treat as a handle and look it up
handle := strings.TrimPrefix(input, "@")
var id uuid.UUID
err := h.pool.QueryRow(ctx, `SELECT id FROM profiles WHERE handle = $1`, handle).Scan(&id)
if err != nil {
return "", fmt.Errorf("user not found: %s", input)
}
return id.String(), nil
}
// AdminManageFollow adds or removes follow relationships for official accounts. // AdminManageFollow adds or removes follow relationships for official accounts.
func (h *AdminHandler) AdminManageFollow(c *gin.Context) { func (h *AdminHandler) AdminManageFollow(c *gin.Context) {
ctx := c.Request.Context() ctx := c.Request.Context()
@ -696,7 +712,7 @@ func (h *AdminHandler) AdminManageFollow(c *gin.Context) {
var req struct { var req struct {
Action string `json:"action"` // "add" or "remove" Action string `json:"action"` // "add" or "remove"
UserID string `json:"user_id"` // the other user in the relationship UserID string `json:"user_id"` // UUID or handle of the other user
Relation string `json:"relation"` // "follower" (user follows target) or "following" (target follows user) Relation string `json:"relation"` // "follower" (user follows target) or "following" (target follows user)
} }
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
@ -713,16 +729,21 @@ func (h *AdminHandler) AdminManageFollow(c *gin.Context) {
return return
} }
// Resolve handle or UUID to a UUID
resolvedID, err := h.resolveUserID(ctx, req.UserID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Determine follower_id and following_id // Determine follower_id and following_id
var followerID, followingID string var followerID, followingID string
if req.Relation == "follower" { if req.Relation == "follower" {
// "user follows target" — user is follower, target is following followerID = resolvedID
followerID = req.UserID
followingID = targetUserID followingID = targetUserID
} else { } else {
// "target follows user" — target is follower, user is following
followerID = targetUserID followerID = targetUserID
followingID = req.UserID followingID = resolvedID
} }
if req.Action == "add" { if req.Action == "add" {