feat: Implement document permission handling and sharing features

This commit is contained in:
M1ngdaXie
2026-01-10 21:19:12 -08:00
parent 6ba18854bf
commit 6b1ed8d11c
13 changed files with 340 additions and 31 deletions

View File

@@ -233,4 +233,111 @@ func (h *DocumentHandler) DeleteDocument(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{"message": "Document deleted successfully"})
}
// GetDocumentPermission returns the user's permission level for a document
func (h *DocumentHandler) GetDocumentPermission(c *gin.Context) {
documentID, err := uuid.Parse(c.Param("id"))
if err != nil {
respondBadRequest(c, "Invalid document ID format")
return
}
// 1. 先检查文档是否存在 (Good practice)
_, err = h.store.GetDocument(documentID)
if err != nil {
respondNotFound(c, "document")
return
}
userID := auth.GetUserFromContext(c)
shareToken := c.Query("share")
// 定义两个临时变量,用来存两边的结果
var userPerm string // 存 document_shares 的结果
var tokenPerm string // 存 share_token 的结果
// ====================================================
// 步骤 A: 检查个人权限 (Base Permission)
// ====================================================
if userID != nil {
perm, err := h.store.GetUserPermission(c.Request.Context(), documentID, *userID)
if err != nil {
respondInternalError(c, "Failed to get user permission", err)
return
}
userPerm = perm
// ⚠️ 注意:如果 perm 是空,这里不报错!继续往下走!
}
// ====================================================
// 步骤 B: 检查 Token 权限 (Upgrade Permission)
// ====================================================
if shareToken != "" {
// 先验证 Token 是否有效
valid, err := h.store.ValidateShareToken(c.Request.Context(), documentID, shareToken)
if err != nil {
respondInternalError(c, "Failed to validate token", err)
return
}
// 只有 Token 有效才去取权限
if valid {
p, err := h.store.GetShareLinkPermission(c.Request.Context(), documentID)
if err != nil {
respondInternalError(c, "Failed to get token permission", err)
return
}
tokenPerm = p
// 处理数据库老数据的 fallback
if tokenPerm == "" { tokenPerm = "view" }
}
}
// ====================================================
// 步骤 C: ⚡️ 权限合并与计算 (The Brain)
// ====================================================
finalPermission := ""
role := "viewer" // 默认角色
// 1. 如果是 Owner无敌直接返回
if userPerm == "owner" {
finalPermission = "edit"
role = "owner"
// 直接返回,不用看 Token 了
c.JSON(http.StatusOK, models.PermissionResponse{
Permission: finalPermission,
Role: role,
})
return
}
// 2. 比较 User 和 Token取最大值
// 逻辑:只要任意一边给了 "edit",那就是 "edit"
if userPerm == "edit" || tokenPerm == "edit" {
finalPermission = "edit"
role = "editor"
} else if userPerm == "view" || tokenPerm == "view" {
finalPermission = "view"
role = "viewer"
}
// ====================================================
// 步骤 D: 最终判决
// ====================================================
if finalPermission == "" {
// 既没个人权限Token 也不对(或者没 Token
if userID == nil {
respondUnauthorized(c, "Authentication required") // 没登录且没Token
} else {
respondForbidden(c, "You don't have permission") // 登录了但没权限
}
return
}
c.JSON(http.StatusOK, models.PermissionResponse{
Permission: finalPermission,
Role: role,
})
}

View File

@@ -243,6 +243,16 @@ func (h *ShareHandler) GetShareLink(c *gin.Context) {
return
}
// Get the permission for the share link
permission, err := h.store.GetShareLinkPermission(c.Request.Context(), documentID)
if err != nil {
respondInternalError(c, "Failed to get share link permission", err)
return
}
if permission == "" {
permission = "edit" // Default fallback
}
frontendURL := os.Getenv("FRONTEND_URL")
if frontendURL == "" {
frontendURL = "http://localhost:5173"
@@ -251,8 +261,9 @@ func (h *ShareHandler) GetShareLink(c *gin.Context) {
shareURL := fmt.Sprintf("%s/editor/%s?share=%s", frontendURL, documentID.String(), token)
c.JSON(http.StatusOK, gin.H{
"url": shareURL,
"token": token,
"url": shareURL,
"token": token,
"permission": permission,
})
}

View File

@@ -114,18 +114,35 @@ func (wsh *WebSocketHandler) HandleWebSocket(c *gin.Context) {
return
}
// If authenticated with JWT, check document permissions
// Determine permission level
var permission string
if userID != nil {
canView, err := wsh.store.CanViewDocument(c.Request.Context(), documentID, *userID)
// Authenticated user - get their permission level
perm, err := wsh.store.GetUserPermission(c.Request.Context(), documentID, *userID)
if err != nil {
log.Printf("Error checking permissions: %v", err)
log.Printf("Error getting user permission: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check permissions"})
return
}
if !canView {
if perm == "" {
c.JSON(http.StatusForbidden, gin.H{"error": "You don't have permission to access this document"})
return
}
permission = perm
} else {
// Share token user - get share link permission
perm, err := wsh.store.GetShareLinkPermission(c.Request.Context(), documentID)
if err != nil {
log.Printf("Error getting share link permission: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check permissions"})
return
}
if perm == "" {
// Share link doesn't exist or document isn't public
c.JSON(http.StatusForbidden, gin.H{"error": "Invalid share link"})
return
}
permission = perm
}
// Upgrade connection
@@ -135,9 +152,9 @@ func (wsh *WebSocketHandler) HandleWebSocket(c *gin.Context) {
return
}
// Create client with user information
// Create client with user information and permission
clientID := uuid.New().String()
client := hub.NewClient(clientID, userID, userName, userAvatar, conn, wsh.hub, roomID)
client := hub.NewClient(clientID, userID, userName, userAvatar, permission, conn, wsh.hub, roomID)
// Register client
wsh.hub.Register <- client