Notion Coding题目解析Page Permission

Viewed 56

Notion的题库不大,很多题目在某论坛都能找到,但是大部分人发面经从来不发解答,甚至连题目都不发全,我在这里把一些题目的详细解答都写出来,希望对大家有帮助。

这个帖子我来写一道关于Page Permission管理的题目。

题目

Notion的一个Workspace里有一系列的页面(page), 每个页面有一组权限(permissions),还可以有一个父页面。用TypeScript定义的数据结构如下

type Page = {
  parent: Page | undefined
  permissions: Permission[]
}

权限有两种,一种是单个用户的,另一种是用户组的。如果是用户组权限,那么所有在这个用户组里的用户都有相同的权限。

如果一个页面的没有包含某个用户的权限设置,那么它会继承这个用户在父页面的权限。

如果一个页面的没有包含某个用户的权限设置,但是这个用户在页面包含的用户组权限里,那么这个用户组的权限会覆盖所有父页面关于这个用户的权限。

有一个帮助函数,已经实现好了,本质上就是判断一系列用户组里权限最高的那个。优先级从高到低依次是Edit, Comment, View.

getMostPermissiveRole(roles: PermissionRole[]): PermissionRole

第一部分是写一个函数,给出一个用户和一个页面,计算这个用户最高的权限。

function calculateRoleForUser(userId: string, page: Page): PermissionRole {
   // TODO: Not implemented yet.
   return "view"
}

第二部分是写一个函数,给出一个页面,返回所有可以查阅(view)这个页面的用户。

function getUserIdsThatCanViewPage(page: Page): Array<string> {
   // TODO: Not implemented 
   return []
}

讨论

这题并不难,主要的挑战是需要自己给出测试样例,尽可能覆盖更多的输入。另外有很多边界情况需要跟面试官讨论清楚,不要盲目开始写代码。我会在评论区一楼给出一个参考的实现。

1 Answers

参考实现

下面是题目给出的现有的代码

class Permission:
    def __init__(self, role):
        self.role = role

class UserPermission(Permission):
    def __init__(self, user_id, role):
        self.user_id = user_id
        self.role = role
    
class GroupPermission(Permission):
    def __init__(self, user_ids, role):
        self.user_ids = user_ids
        self.role = role

class Page:
    def __init__(self, parent, permissions):
        self.parent = parent
        self.permissions = permissions

def getMostPermissiveRole(roles):
    """This function is given by the interviewer"""
    unique = set(roles)
    if PermissionRole.EDIT in unique:
        return PermissionRole.EDIT
    if PermissionRole.COMMENT in unique:
        return PermissionRole.COMMENT
    if PermissionRole.VIEW in unique: 
        return PermissionRole.VIEW
    return PermissionRole.NONE

第一部分

第一部分比较简单,先检索当前页面所有的权限,判断是否有符合当前用户的角色(role)并记录下来,然后从这些角色中找出权限最高的那个。如果当前页面没有符合该用户的角色,就递归地向父页面搜索。这里不需要另外实现帮助函数,使用题目给出的函数原型即可进行递归。

def calculateRoleForUser(user_id, page):
    if page.permissions:
        roles = []
        for permission in page.permissions:
            if isinstance(permission, UserPermission) and user_id == permission.user_id:
                roles.append(permission.role)
            elif isinstance(permission, GroupPermission) and user_id in permission.user_ids:
                roles.append(permission.role)
        if roles:
            return getMostPermissiveRole(roles)
    if page.parent:
        return calculateRoleForUser(user_id, page.parent)
    return PermissionRole.NONE

第二部分

第二部分略微有点小麻烦。因为一个页面的权限里,可以显式地把某个用户的权限设置为None, 也就是说,阻止该用户访问页面。这里需要用到一个set来存储那些被显式阻止的用户。这部分的主体依然用递归来实现,但是需要单独写一个帮助函数。宏观上就是逐一检查每个页面及其父页面的权限,把有阅读权限的用户记下来,但是把那些被阻止的用户排除掉,最后返回全部的结果。

def get_user_ids_can_view_page(page):
    users = set()
    blocked_users = set()
    
    def check_permissions(page):
        if page.permissions:
            for permission in page.permissions:
                if isinstance(permission, UserPermission):
                    if permission.role == PermissionRole.NONE:
                        blocked_users.add(permission.user_id)
                    elif permission.user_id not in blocked_users:
                        users.add(permission.user_id)
                elif isinstance(permission, GroupPermission):
                    if permission.role == PermissionRole.NONE:
                        blocked_users.update(permission.user_ids)
                    else:
                        for user in permission.user_ids:
                            if user not in blocked_users:
                                users.add(user)
                                
        if page.parent:
            check_permissions(page.parent)
    
    check_permissions(page)
    return list(users)
Related Experiences