mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: add linking UGs to Users
This commit is contained in:
parent
d1d817678b
commit
e173a32e3c
9 changed files with 235 additions and 20 deletions
|
|
@ -1,22 +1,20 @@
|
||||||
from typing import Literal
|
from fastapi import APIRouter, Depends, Request
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile
|
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
from src.services.users.users import delete_user_by_id
|
|
||||||
from src.db.usergroups import UserGroupCreate, UserGroupRead, UserGroupUpdate
|
from src.db.usergroups import UserGroupCreate, UserGroupRead, UserGroupUpdate
|
||||||
from src.db.users import PublicUser
|
from src.db.users import PublicUser, UserRead
|
||||||
from src.services.users.usergroups import (
|
from src.services.users.usergroups import (
|
||||||
add_resources_to_usergroup,
|
add_resources_to_usergroup,
|
||||||
add_users_to_usergroup,
|
add_users_to_usergroup,
|
||||||
create_usergroup,
|
create_usergroup,
|
||||||
delete_usergroup_by_id,
|
delete_usergroup_by_id,
|
||||||
get_usergroups_by_resource,
|
get_usergroups_by_resource,
|
||||||
|
get_users_linked_to_usergroup,
|
||||||
read_usergroup_by_id,
|
read_usergroup_by_id,
|
||||||
read_usergroups_by_org_id,
|
read_usergroups_by_org_id,
|
||||||
remove_resources_from_usergroup,
|
remove_resources_from_usergroup,
|
||||||
remove_users_from_usergroup,
|
remove_users_from_usergroup,
|
||||||
update_usergroup_by_id,
|
update_usergroup_by_id,
|
||||||
)
|
)
|
||||||
from src.services.orgs.orgs import get_org_join_mechanism
|
|
||||||
from src.security.auth import get_current_user
|
from src.security.auth import get_current_user
|
||||||
from src.core.events.database import get_db_session
|
from src.core.events.database import get_db_session
|
||||||
|
|
||||||
|
|
@ -52,6 +50,22 @@ async def api_get_usergroup(
|
||||||
return await read_usergroup_by_id(request, db_session, current_user, usergroup_id)
|
return await read_usergroup_by_id(request, db_session, current_user, usergroup_id)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{usergroup_id}/users", response_model=list[UserRead], tags=["usergroups"])
|
||||||
|
async def api_get_users_linked_to_usergroup(
|
||||||
|
*,
|
||||||
|
request: Request,
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
current_user: PublicUser = Depends(get_current_user),
|
||||||
|
usergroup_id: int,
|
||||||
|
) -> list[UserRead]:
|
||||||
|
"""
|
||||||
|
Get Users linked to UserGroup
|
||||||
|
"""
|
||||||
|
return await get_users_linked_to_usergroup(
|
||||||
|
request, db_session, current_user, usergroup_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/org/{org_id}", response_model=list[UserGroupRead], tags=["usergroups"])
|
@router.get("/org/{org_id}", response_model=list[UserGroupRead], tags=["usergroups"])
|
||||||
async def api_get_usergroups(
|
async def api_get_usergroups(
|
||||||
*,
|
*,
|
||||||
|
|
@ -65,7 +79,10 @@ async def api_get_usergroups(
|
||||||
"""
|
"""
|
||||||
return await read_usergroups_by_org_id(request, db_session, current_user, org_id)
|
return await read_usergroups_by_org_id(request, db_session, current_user, org_id)
|
||||||
|
|
||||||
@router.get("/resource/{resource_uuid}", response_model=list[UserGroupRead], tags=["usergroups"])
|
|
||||||
|
@router.get(
|
||||||
|
"/resource/{resource_uuid}", response_model=list[UserGroupRead], tags=["usergroups"]
|
||||||
|
)
|
||||||
async def api_get_usergroupsby_resource(
|
async def api_get_usergroupsby_resource(
|
||||||
*,
|
*,
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
@ -76,7 +93,9 @@ async def api_get_usergroupsby_resource(
|
||||||
"""
|
"""
|
||||||
Get UserGroups by Org
|
Get UserGroups by Org
|
||||||
"""
|
"""
|
||||||
return await get_usergroups_by_resource(request, db_session, current_user, resource_uuid)
|
return await get_usergroups_by_resource(
|
||||||
|
request, db_session, current_user, resource_uuid
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{usergroup_id}", response_model=UserGroupRead, tags=["usergroups"])
|
@router.put("/{usergroup_id}", response_model=UserGroupRead, tags=["usergroups"])
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
import stat
|
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from regex import R
|
from sqlalchemy import union
|
||||||
from sqlalchemy import exists, union
|
from sqlmodel import Session, select
|
||||||
from sqlmodel import Session, select, and_, or_
|
|
||||||
from src.db.usergroups import UserGroup
|
|
||||||
from src.db.usergroup_resources import UserGroupResource
|
from src.db.usergroup_resources import UserGroupResource
|
||||||
from src.db.usergroup_user import UserGroupUser
|
from src.db.usergroup_user import UserGroupUser
|
||||||
from src.db.organizations import Organization
|
from src.db.organizations import Organization
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,13 @@ from fastapi import HTTPException, Request
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
from src.security.rbac.rbac import (
|
from src.security.rbac.rbac import (
|
||||||
authorization_verify_based_on_roles_and_authorship_and_usergroups,
|
authorization_verify_based_on_roles_and_authorship_and_usergroups,
|
||||||
authorization_verify_if_element_is_public,
|
|
||||||
authorization_verify_if_user_is_anon,
|
authorization_verify_if_user_is_anon,
|
||||||
)
|
)
|
||||||
from src.db.usergroup_resources import UserGroupResource
|
from src.db.usergroup_resources import UserGroupResource
|
||||||
from src.db.usergroup_user import UserGroupUser
|
from src.db.usergroup_user import UserGroupUser
|
||||||
from src.db.organizations import Organization
|
from src.db.organizations import Organization
|
||||||
from src.db.usergroups import UserGroup, UserGroupCreate, UserGroupRead, UserGroupUpdate
|
from src.db.usergroups import UserGroup, UserGroupCreate, UserGroupRead, UserGroupUpdate
|
||||||
from src.db.users import AnonymousUser, PublicUser, User
|
from src.db.users import AnonymousUser, PublicUser, User, UserRead
|
||||||
|
|
||||||
|
|
||||||
async def create_usergroup(
|
async def create_usergroup(
|
||||||
|
|
@ -89,6 +88,48 @@ async def read_usergroup_by_id(
|
||||||
return usergroup
|
return usergroup
|
||||||
|
|
||||||
|
|
||||||
|
async def get_users_linked_to_usergroup(
|
||||||
|
request: Request,
|
||||||
|
db_session: Session,
|
||||||
|
current_user: PublicUser | AnonymousUser,
|
||||||
|
usergroup_id: int,
|
||||||
|
) -> list[UserRead]:
|
||||||
|
|
||||||
|
statement = select(UserGroup).where(UserGroup.id == usergroup_id)
|
||||||
|
usergroup = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
if not usergroup:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail="UserGroup not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# RBAC check
|
||||||
|
await rbac_check(
|
||||||
|
request,
|
||||||
|
usergroup_uuid=usergroup.usergroup_uuid,
|
||||||
|
current_user=current_user,
|
||||||
|
action="read",
|
||||||
|
db_session=db_session,
|
||||||
|
)
|
||||||
|
|
||||||
|
statement = select(UserGroupUser).where(UserGroupUser.usergroup_id == usergroup_id)
|
||||||
|
usergroup_users = db_session.exec(statement).all()
|
||||||
|
|
||||||
|
user_ids = [usergroup_user.user_id for usergroup_user in usergroup_users]
|
||||||
|
|
||||||
|
# get users
|
||||||
|
users = []
|
||||||
|
for user_id in user_ids:
|
||||||
|
statement = select(User).where(User.id == user_id)
|
||||||
|
user = db_session.exec(statement).first()
|
||||||
|
users.append(user)
|
||||||
|
|
||||||
|
users = [UserRead.from_orm(user) for user in users]
|
||||||
|
|
||||||
|
return users
|
||||||
|
|
||||||
|
|
||||||
async def read_usergroups_by_org_id(
|
async def read_usergroups_by_org_id(
|
||||||
request: Request,
|
request: Request,
|
||||||
db_session: Session,
|
db_session: Session,
|
||||||
|
|
@ -99,8 +140,6 @@ async def read_usergroups_by_org_id(
|
||||||
statement = select(UserGroup).where(UserGroup.org_id == org_id)
|
statement = select(UserGroup).where(UserGroup.org_id == org_id)
|
||||||
usergroups = db_session.exec(statement).all()
|
usergroups = db_session.exec(statement).all()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# RBAC check
|
# RBAC check
|
||||||
await rbac_check(
|
await rbac_check(
|
||||||
request,
|
request,
|
||||||
|
|
@ -237,6 +276,8 @@ async def add_users_to_usergroup(
|
||||||
detail="UserGroup not found",
|
detail="UserGroup not found",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# RBAC check
|
# RBAC check
|
||||||
await rbac_check(
|
await rbac_check(
|
||||||
request,
|
request,
|
||||||
|
|
@ -252,6 +293,17 @@ async def add_users_to_usergroup(
|
||||||
statement = select(User).where(User.id == user_id)
|
statement = select(User).where(User.id == user_id)
|
||||||
user = db_session.exec(statement).first()
|
user = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
# Check if User is already Linked to UserGroup
|
||||||
|
statement = select(UserGroupUser).where(
|
||||||
|
UserGroupUser.usergroup_id == usergroup_id,
|
||||||
|
UserGroupUser.user_id == user_id,
|
||||||
|
)
|
||||||
|
usergroup_user = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
if usergroup_user:
|
||||||
|
logging.error(f"User with id {user_id} already exists in UserGroup")
|
||||||
|
continue
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
# Add user to UserGroup
|
# Add user to UserGroup
|
||||||
if user.id is not None:
|
if user.id is not None:
|
||||||
|
|
@ -301,7 +353,7 @@ async def remove_users_from_usergroup(
|
||||||
user_ids_array = user_ids.split(",")
|
user_ids_array = user_ids.split(",")
|
||||||
|
|
||||||
for user_id in user_ids_array:
|
for user_id in user_ids_array:
|
||||||
statement = select(UserGroupUser).where(UserGroupUser.user_id == user_id)
|
statement = select(UserGroupUser).where(UserGroupUser.user_id == user_id, UserGroupUser.usergroup_id == usergroup_id)
|
||||||
usergroup_user = db_session.exec(statement).first()
|
usergroup_user = db_session.exec(statement).first()
|
||||||
|
|
||||||
if usergroup_user:
|
if usergroup_user:
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ function InviteOnlySignUpComponent(props: InviteOnlySignUpProps) {
|
||||||
last_name: '',
|
last_name: '',
|
||||||
},
|
},
|
||||||
validate,
|
validate,
|
||||||
|
enableReinitialize: true,
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
setError('')
|
setError('')
|
||||||
setMessage('')
|
setMessage('')
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ function OpenSignUpComponent() {
|
||||||
last_name: '',
|
last_name: '',
|
||||||
},
|
},
|
||||||
validate,
|
validate,
|
||||||
|
enableReinitialize: true,
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
setError('')
|
setError('')
|
||||||
setMessage('')
|
setMessage('')
|
||||||
|
|
|
||||||
|
|
@ -74,9 +74,11 @@ function OrgUserGroups() {
|
||||||
onOpenChange={() =>
|
onOpenChange={() =>
|
||||||
handleUserGroupManagementModal(usergroup.id)
|
handleUserGroupManagementModal(usergroup.id)
|
||||||
}
|
}
|
||||||
minHeight="no-min"
|
minHeight="lg"
|
||||||
|
minWidth='lg'
|
||||||
dialogContent={
|
dialogContent={
|
||||||
<ManageUsers
|
<ManageUsers
|
||||||
|
usergroup_id={usergroup.id}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
dialogTitle="Manage UserGroup Users"
|
dialogTitle="Manage UserGroup Users"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,114 @@
|
||||||
|
import { useOrg } from '@components/Contexts/OrgContext'
|
||||||
|
import { getAPIUrl } from '@services/config/config'
|
||||||
|
import { linkUserToUserGroup, unLinkUserToUserGroup } from '@services/usergroups/usergroups'
|
||||||
|
import { swrFetcher } from '@services/utils/ts/requests'
|
||||||
|
import { Check, Plus, X } from 'lucide-react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
import useSWR, { mutate } from 'swr'
|
||||||
|
|
||||||
|
|
||||||
|
type ManageUsersProps = {
|
||||||
|
usergroup_id: any
|
||||||
|
}
|
||||||
|
|
||||||
|
function ManageUsers(props: ManageUsersProps) {
|
||||||
|
const org = useOrg() as any
|
||||||
|
const { data: OrgUsers } = useSWR(
|
||||||
|
org ? `${getAPIUrl()}orgs/${org.id}/users` : null,
|
||||||
|
swrFetcher
|
||||||
|
)
|
||||||
|
const { data: UGusers } = useSWR(
|
||||||
|
org ? `${getAPIUrl()}usergroups/${props.usergroup_id}/users` : null,
|
||||||
|
swrFetcher
|
||||||
|
)
|
||||||
|
|
||||||
|
const isUserPartOfGroup = (user_id: any) => {
|
||||||
|
if (UGusers) {
|
||||||
|
return UGusers.some((user: any) => user.id === user_id)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLinkUser = async (user_id: any) => {
|
||||||
|
const res = await linkUserToUserGroup(props.usergroup_id, user_id)
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success('User linked successfully')
|
||||||
|
mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`)
|
||||||
|
} else {
|
||||||
|
toast.error('Error ' + res.status + ': ' + res.data.detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUnlinkUser = async (user_id: any) => {
|
||||||
|
const res = await unLinkUserToUserGroup(props.usergroup_id, user_id)
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success('User unlinked successfully')
|
||||||
|
mutate(`${getAPIUrl()}usergroups/${props.usergroup_id}/users`)
|
||||||
|
} else {
|
||||||
|
toast.error('Error ' + res.status + ': ' + res.data.detail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function ManageUsers() {
|
|
||||||
return (
|
return (
|
||||||
<div>ManageUsers</div>
|
<div className='py-3'>
|
||||||
|
<table className="table-auto w-full text-left whitespace-nowrap rounded-md overflow-hidden">
|
||||||
|
<thead className="bg-gray-100 text-gray-500 rounded-xl uppercase">
|
||||||
|
<tr className="font-bolder text-sm">
|
||||||
|
<th className="py-3 px-4">User</th>
|
||||||
|
<th className="py-3 px-4">Linked</th>
|
||||||
|
<th className="py-3 px-4">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<>
|
||||||
|
<tbody className="mt-5 bg-white rounded-md">
|
||||||
|
{OrgUsers?.map((user: any) => (
|
||||||
|
<tr
|
||||||
|
key={user.user.id}
|
||||||
|
className="border-b border-gray-200 border-dashed text-sm"
|
||||||
|
>
|
||||||
|
<td className="py-3 px-4 flex space-x-2 items-center">
|
||||||
|
<span>
|
||||||
|
{user.user.first_name + ' ' + user.user.last_name}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs bg-neutral-100 p-1 px-2 rounded-full text-neutral-400 font-semibold">
|
||||||
|
@{user.user.username}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="py-3 px-4">
|
||||||
|
{isUserPartOfGroup(user.user.id) ?
|
||||||
|
<div className="space-x-1 flex w-fit px-4 py-1 bg-cyan-100 rounded-full items-center text-cyan-800">
|
||||||
|
<Check size={16} />
|
||||||
|
<span>Linked</span>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div className="space-x-1 flex w-fit px-4 py-1 bg-gray-100 rounded-full items-center text-gray-800">
|
||||||
|
<X size={16} />
|
||||||
|
<span>Not linked</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td className="py-3 px-4 flex space-x-2 items-end">
|
||||||
|
<button
|
||||||
|
onClick={() => handleLinkUser(user.user.id)}
|
||||||
|
className="flex space-x-2 hover:cursor-pointer p-1 px-3 bg-cyan-700 rounded-md font-bold items-center text-sm text-cyan-100">
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
<span> Link</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleUnlinkUser(user.user.id)}
|
||||||
|
className="flex space-x-2 hover:cursor-pointer p-1 px-3 bg-gray-700 rounded-md font-bold items-center text-sm text-gray-100">
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
<span> Unlink</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ type ModalParams = {
|
||||||
onOpenChange: any
|
onOpenChange: any
|
||||||
isDialogOpen?: boolean
|
isDialogOpen?: boolean
|
||||||
minHeight?: 'sm' | 'md' | 'lg' | 'xl' | 'no-min'
|
minHeight?: 'sm' | 'md' | 'lg' | 'xl' | 'no-min'
|
||||||
|
minWidth?: 'sm' | 'md' | 'lg' | 'xl' | 'no-min'
|
||||||
}
|
}
|
||||||
|
|
||||||
const Modal = (params: ModalParams) => (
|
const Modal = (params: ModalParams) => (
|
||||||
|
|
@ -28,6 +29,7 @@ const Modal = (params: ModalParams) => (
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="overflow-auto scrollbar-w-2 scrollbar-h-2 scrollbar scrollbar-thumb-black/20 scrollbar-thumb-rounded-full scrollbar-track-rounded-full"
|
className="overflow-auto scrollbar-w-2 scrollbar-h-2 scrollbar scrollbar-thumb-black/20 scrollbar-thumb-rounded-full scrollbar-track-rounded-full"
|
||||||
minHeight={params.minHeight}
|
minHeight={params.minHeight}
|
||||||
|
minWidth={params.minWidth}
|
||||||
>
|
>
|
||||||
<DialogTopBar className="-space-y-1">
|
<DialogTopBar className="-space-y-1">
|
||||||
<DialogTitle>{params.dialogTitle}</DialogTitle>
|
<DialogTitle>{params.dialogTitle}</DialogTitle>
|
||||||
|
|
@ -103,6 +105,23 @@ const DialogContent = styled(Dialog.Content, {
|
||||||
minHeight: '900px',
|
minHeight: '900px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
minWidth: {
|
||||||
|
'no-min': {
|
||||||
|
minWidth: '0px',
|
||||||
|
},
|
||||||
|
sm: {
|
||||||
|
minWidth: '600px',
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
minWidth: '800px',
|
||||||
|
},
|
||||||
|
lg: {
|
||||||
|
minWidth: '1000px',
|
||||||
|
},
|
||||||
|
xl: {
|
||||||
|
minWidth: '1200px',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,24 @@ export async function createUserGroup(body: any) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function linkUserToUserGroup(usergroup_id: any, user_id: any) {
|
||||||
|
const result: any = await fetch(
|
||||||
|
`${getAPIUrl()}usergroups/${usergroup_id}/add_users?user_ids=${user_id}`,
|
||||||
|
RequestBody('POST', null, null)
|
||||||
|
)
|
||||||
|
const res = await getResponseMetadata(result)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unLinkUserToUserGroup(usergroup_id: any, user_id: any) {
|
||||||
|
const result: any = await fetch(
|
||||||
|
`${getAPIUrl()}usergroups/${usergroup_id}/remove_users?user_ids=${user_id}`,
|
||||||
|
RequestBody('DELETE', null, null)
|
||||||
|
)
|
||||||
|
const res = await getResponseMetadata(result)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
export async function deleteUserGroup(usergroup_id: number) {
|
export async function deleteUserGroup(usergroup_id: number) {
|
||||||
const result: any = await fetch(
|
const result: any = await fetch(
|
||||||
`${getAPIUrl()}usergroups/${usergroup_id}`,
|
`${getAPIUrl()}usergroups/${usergroup_id}`,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue