diff --git a/apps/api/src/routers/auth.py b/apps/api/src/routers/auth.py index ff7ea5c6..f13bc033 100644 --- a/apps/api/src/routers/auth.py +++ b/apps/api/src/routers/auth.py @@ -4,11 +4,11 @@ from fastapi import Depends, APIRouter, HTTPException, Response, status, Request from fastapi.security import OAuth2PasswordRequestForm from pydantic import BaseModel, EmailStr from sqlmodel import Session -from src.db.users import AnonymousUser, PublicUser, UserRead +from src.db.users import AnonymousUser, UserRead from src.core.events.database import get_db_session from config.config import get_learnhouse_config from src.security.auth import AuthJWT, authenticate_user, get_current_user -from src.services.auth.utils import get_google_user_info, signWithGoogle +from src.services.auth.utils import signWithGoogle router = APIRouter() diff --git a/apps/api/src/routers/orgs.py b/apps/api/src/routers/orgs.py index cd7f7d9b..ad345649 100644 --- a/apps/api/src/routers/orgs.py +++ b/apps/api/src/routers/orgs.py @@ -8,6 +8,7 @@ from src.services.orgs.invites import ( get_invite_code, get_invite_codes, ) +from src.services.orgs.join import JoinOrg, join_org from src.services.orgs.users import ( get_list_of_invited_users, get_organization_users, @@ -99,6 +100,19 @@ async def api_get_org_users( return await get_organization_users(request, org_id, db_session, current_user) +@router.post("/join") +async def api_join_an_org( + request: Request, + args: JoinOrg, + current_user: PublicUser = Depends(get_current_user), + db_session: Session = Depends(get_db_session), +): + """ + Get single Org by ID + """ + return await join_org(request, args, current_user, db_session) + + @router.put("/{org_id}/users/{user_id}/role/{role_uuid}") async def api_update_user_role( request: Request, diff --git a/apps/api/src/routers/users.py b/apps/api/src/routers/users.py index 5dc621f3..98cb25aa 100644 --- a/apps/api/src/routers/users.py +++ b/apps/api/src/routers/users.py @@ -85,7 +85,6 @@ async def api_create_user_with_orgid( """ Create User with Org ID """ - print(await get_org_join_mechanism(request, org_id, current_user, db_session)) # TODO(fix) : This is temporary, logic should be moved to service if ( diff --git a/apps/api/src/services/orgs/join.py b/apps/api/src/services/orgs/join.py new file mode 100644 index 00000000..177cc04e --- /dev/null +++ b/apps/api/src/services/orgs/join.py @@ -0,0 +1,119 @@ +from datetime import datetime +from typing import Optional +from fastapi import HTTPException, Request +from pydantic import BaseModel +from sqlmodel import Session, select +from src.db.organizations import Organization +from src.db.user_organizations import UserOrganization +from src.db.users import AnonymousUser, PublicUser, User +from src.services.orgs.invites import get_invite_code +from src.services.orgs.orgs import get_org_join_mechanism + + +class JoinOrg(BaseModel): + org_id: int + user_id: str + invite_code: Optional[str] + + +async def join_org( + request: Request, + args: JoinOrg, + current_user: PublicUser | AnonymousUser, + db_session: Session, +): + statement = select(Organization).where(Organization.id == args.org_id) + result = db_session.exec(statement) + + org = result.first() + + if not org: + raise HTTPException( + status_code=404, + detail="Organization not found", + ) + + join_method = await get_org_join_mechanism( + request, args.org_id, current_user, db_session + ) + + # Get User + statement = select(User).where(User.id == args.user_id) + result = db_session.exec(statement) + + user = result.first() + + # Check if User isn't already part of the org + statement = select(UserOrganization).where( + UserOrganization.user_id == args.user_id, UserOrganization.org_id == args.org_id + ) + result = db_session.exec(statement) + + userorg = result.first() + + if userorg: + raise HTTPException( + status_code=400, detail="User is already part of that organization" + ) + + if join_method == "inviteOnly" and user and org and args.invite_code: + if user.id is not None and org.id is not None: + + # Check if invite code exists + inviteCode = await get_invite_code( + request, org.id, args.invite_code, current_user, db_session + ) + + if not inviteCode: + raise HTTPException( + status_code=400, + detail="Invite code is incorrect", + ) + + # Link user and organization + user_organization = UserOrganization( + user_id=user.id, + org_id=org.id, + role_id=3, + creation_date=str(datetime.now()), + update_date=str(datetime.now()), + ) + + db_session.add(user_organization) + db_session.commit() + + return "Great, You're part of the Organization" + + else: + raise HTTPException( + status_code=403, + detail="Something wrong, try later.", + ) + + if join_method == "open" and user and org: + if user.id is not None and org.id is not None: + # Link user and organization + user_organization = UserOrganization( + user_id=user.id, + org_id=org.id, + role_id=3, + creation_date=str(datetime.now()), + update_date=str(datetime.now()), + ) + + db_session.add(user_organization) + db_session.commit() + + return "Great, You're part of the Organization" + + else: + raise HTTPException( + status_code=403, + detail="Something wrong, try later.", + ) + + else: + raise HTTPException( + status_code=403, + detail="Something wrong, try later.", + ) diff --git a/apps/api/src/services/orgs/orgs.py b/apps/api/src/services/orgs/orgs.py index bd96d22b..d7923ae9 100644 --- a/apps/api/src/services/orgs/orgs.py +++ b/apps/api/src/services/orgs/orgs.py @@ -436,8 +436,7 @@ async def get_orgs_by_user( orgs = result.all() - return orgs - + return orgs #type:ignore # Config related async def update_org_signup_mechanism( diff --git a/apps/web/app/auth/signup/signup.tsx b/apps/web/app/auth/signup/signup.tsx index 3eb1b2e2..933fd53f 100644 --- a/apps/web/app/auth/signup/signup.tsx +++ b/apps/web/app/auth/signup/signup.tsx @@ -16,6 +16,8 @@ import { validateInviteCode } from '@services/organizations/invites' import PageLoading from '@components/Objects/Loaders/PageLoading' import Toast from '@components/StyledElements/Toast/Toast' import toast from 'react-hot-toast' +import { BarLoader } from 'react-spinners' +import { joinOrg } from '@services/organizations/orgs' interface SignUpClientProps { org: any @@ -97,7 +99,7 @@ function SignUpClient(props: SignUpClientProps) { {joinMethod == 'inviteOnly' && (inviteCode ? ( session.status == 'authenticated' ? ( - + ) : ( ) @@ -112,7 +114,30 @@ function SignUpClient(props: SignUpClientProps) { const LoggedInJoinScreen = (props: any) => { const session = useLHSession() as any const org = useOrg() as any + const invite_code = props.inviteCode const [isLoading, setIsLoading] = React.useState(true) + const [isSumbitting, setIsSubmitting] = React.useState(false) + const router = useRouter() + + const join = async () => { + setIsSubmitting(true) + const res = await joinOrg({ org_id: org.id, user_id: session?.data?.user?.id, invite_code: props.inviteCode }, null, session.data?.tokens?.access_token) + //wait for 1s + if (res.success) { + toast.success( + res.data + ) + setTimeout(() => { + router.push(`/`) + }, 2000) + setIsSubmitting(false) + } else { + toast.error(res.data.detail) + setIsLoading(false) + setIsSubmitting(false) + } + + } useEffect(() => { if (session && org) { @@ -122,6 +147,7 @@ const LoggedInJoinScreen = (props: any) => { return (
+

Hi @@ -131,9 +157,13 @@ const LoggedInJoinScreen = (props: any) => { join {org?.name} ?

-
@@ -154,7 +184,7 @@ const NoTokenScreen = (props: any) => { const validateCode = async () => { setIsLoading(true) - let res = await validateInviteCode(org?.id, inviteCode,session?.user?.tokens.access_token) + let res = await validateInviteCode(org?.id, inviteCode, session?.user?.tokens.access_token) //wait for 1s if (res.success) { toast.success( diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/error.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/error.tsx index bb8f7f65..1e0708da 100644 --- a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/error.tsx +++ b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/error.tsx @@ -1,6 +1,5 @@ 'use client' // Error components must be Client Components -import ErrorUI from '@components/StyledElements/Error/Error' import { useEffect } from 'react' export default function Error({ diff --git a/apps/web/components/Contexts/CourseContext.tsx b/apps/web/components/Contexts/CourseContext.tsx index 585f698a..a2f9f161 100644 --- a/apps/web/components/Contexts/CourseContext.tsx +++ b/apps/web/components/Contexts/CourseContext.tsx @@ -1,5 +1,4 @@ 'use client' -import PageLoading from '@components/Objects/Loaders/PageLoading' import { getAPIUrl } from '@services/config/config' import { swrFetcher } from '@services/utils/ts/requests' import React, { createContext, useContext, useEffect, useReducer } from 'react' diff --git a/apps/web/components/Contexts/OrgContext.tsx b/apps/web/components/Contexts/OrgContext.tsx index 8296fc50..f434b3f1 100644 --- a/apps/web/components/Contexts/OrgContext.tsx +++ b/apps/web/components/Contexts/OrgContext.tsx @@ -1,7 +1,7 @@ 'use client' import { getAPIUrl, getUriWithoutOrg } from '@services/config/config' import { swrFetcher } from '@services/utils/ts/requests' -import React, { createContext, useContext, useEffect, useMemo, useState } from 'react' +import React, { createContext, useContext, useMemo } from 'react' import useSWR from 'swr' import { useLHSession } from '@components/Contexts/LHSessionContext' import ErrorUI from '@components/StyledElements/Error/Error' diff --git a/apps/web/components/Dashboard/UI/LeftMenu.tsx b/apps/web/components/Dashboard/UI/LeftMenu.tsx index ba951506..9f695bbc 100644 --- a/apps/web/components/Dashboard/UI/LeftMenu.tsx +++ b/apps/web/components/Dashboard/UI/LeftMenu.tsx @@ -6,7 +6,6 @@ import LearnHouseDashboardLogo from '@public/dashLogo.png' import { BookCopy, Home, LogOut, School, Settings, Users } from 'lucide-react' import Image from 'next/image' import Link from 'next/link' -import { useRouter } from 'next/navigation' import React, { useEffect } from 'react' import UserAvatar from '../../Objects/UserAvatar' import AdminAuthorization from '@components/Security/AdminAuthorization' diff --git a/apps/web/components/Hooks/useAdminStatus.tsx b/apps/web/components/Hooks/useAdminStatus.tsx index 4a5ee5b3..93ae93d0 100644 --- a/apps/web/components/Hooks/useAdminStatus.tsx +++ b/apps/web/components/Hooks/useAdminStatus.tsx @@ -20,10 +20,12 @@ function useAdminStatus() { const isAdminVar = userRoles.some((role: Role) => { return ( role.org.id === org.id && - (role.role.id === 1 || + ( + role.role.id === 1 || role.role.id === 2 || role.role.role_uuid === 'role_global_admin' || - role.role.role_uuid === 'role_global_maintainer') + role.role.role_uuid === 'role_global_maintainer' + ) ); }); setIsAdmin(isAdminVar); @@ -38,3 +40,4 @@ function useAdminStatus() { } export default useAdminStatus; + diff --git a/apps/web/components/Security/HeaderProfileBox.tsx b/apps/web/components/Security/HeaderProfileBox.tsx index b64b45d7..ca0b9c50 100644 --- a/apps/web/components/Security/HeaderProfileBox.tsx +++ b/apps/web/components/Security/HeaderProfileBox.tsx @@ -7,11 +7,11 @@ import UserAvatar from '@components/Objects/UserAvatar' import useAdminStatus from '@components/Hooks/useAdminStatus' import { useLHSession } from '@components/Contexts/LHSessionContext' import { useOrg } from '@components/Contexts/OrgContext' -import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config' +import { getUriWithoutOrg } from '@services/config/config' export const HeaderProfileBox = () => { const session = useLHSession() as any - const isUserAdmin = useAdminStatus() as any + const isUserAdmin = useAdminStatus() const org = useOrg() as any useEffect(() => { } @@ -37,7 +37,7 @@ export const HeaderProfileBox = () => {

{session.data.user.username}

- {isUserAdmin &&
ADMIN
} + {isUserAdmin.isAdmin &&
ADMIN
}
diff --git a/apps/web/components/StyledElements/Info/Info.tsx b/apps/web/components/StyledElements/Info/Info.tsx index 45492486..a117783a 100644 --- a/apps/web/components/StyledElements/Info/Info.tsx +++ b/apps/web/components/StyledElements/Info/Info.tsx @@ -1,6 +1,6 @@ 'use client' import { getUriWithoutOrg } from '@services/config/config' -import { AlertTriangle, Diamond, Home, PersonStanding, RefreshCcw } from 'lucide-react' +import { Diamond, Home, PersonStanding } from 'lucide-react' import Link from 'next/link' import React from 'react' diff --git a/apps/web/middleware.ts b/apps/web/middleware.ts index 47327727..2234ca51 100644 --- a/apps/web/middleware.ts +++ b/apps/web/middleware.ts @@ -8,7 +8,6 @@ import { } from './services/config/config' import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' -import path from 'path' export const config = { matcher: [ diff --git a/apps/web/services/blocks/Image/images.ts b/apps/web/services/blocks/Image/images.ts index 454d87fa..65331e91 100644 --- a/apps/web/services/blocks/Image/images.ts +++ b/apps/web/services/blocks/Image/images.ts @@ -1,7 +1,5 @@ import { getAPIUrl } from '@services/config/config' import { - RequestBody, - RequestBodyForm, RequestBodyFormWithAuthHeader, RequestBodyWithAuthHeader, } from '@services/utils/ts/requests' diff --git a/apps/web/services/blocks/Pdf/pdf.ts b/apps/web/services/blocks/Pdf/pdf.ts index 99c1e3f0..1f93ce5d 100644 --- a/apps/web/services/blocks/Pdf/pdf.ts +++ b/apps/web/services/blocks/Pdf/pdf.ts @@ -1,7 +1,5 @@ import { getAPIUrl } from '@services/config/config' import { - RequestBody, - RequestBodyForm, RequestBodyFormWithAuthHeader, RequestBodyWithAuthHeader, } from '@services/utils/ts/requests' diff --git a/apps/web/services/blocks/Quiz/quiz.ts b/apps/web/services/blocks/Quiz/quiz.ts index 5d4eb093..b9acae06 100644 --- a/apps/web/services/blocks/Quiz/quiz.ts +++ b/apps/web/services/blocks/Quiz/quiz.ts @@ -1,5 +1,5 @@ import { getAPIUrl } from '@services/config/config' -import { RequestBody, RequestBodyWithAuthHeader } from '@services/utils/ts/requests' +import { RequestBodyWithAuthHeader } from '@services/utils/ts/requests' export async function submitQuizBlock(activity_id: string, data: any,access_token:string) { const result: any = await fetch( diff --git a/apps/web/services/blocks/Video/video.ts b/apps/web/services/blocks/Video/video.ts index ff8e9bd6..6082b080 100644 --- a/apps/web/services/blocks/Video/video.ts +++ b/apps/web/services/blocks/Video/video.ts @@ -1,7 +1,5 @@ import { getAPIUrl } from '@services/config/config' import { - RequestBody, - RequestBodyForm, RequestBodyFormWithAuthHeader, RequestBodyWithAuthHeader, } from '@services/utils/ts/requests' diff --git a/apps/web/services/organizations/orgs.ts b/apps/web/services/organizations/orgs.ts index e8cd4771..9ec5bd1a 100644 --- a/apps/web/services/organizations/orgs.ts +++ b/apps/web/services/organizations/orgs.ts @@ -13,7 +13,7 @@ import { export async function createNewOrganization(body: any, access_token: string) { const result = await fetch( `${getAPIUrl()}orgs/`, - RequestBodyWithAuthHeader('POST', body, null,access_token) + RequestBodyWithAuthHeader('POST', body, null, access_token) ) const res = await errorHandling(result) return res @@ -113,3 +113,20 @@ export async function removeUserFromOrg( const res = await getResponseMetadata(result) return res } + +export async function joinOrg( + args: { + org_id: number + user_id: string + invite_code?: string + }, + next: any, + access_token?: string +) { + const result = await fetch( + `${getAPIUrl()}orgs/join`, + RequestBodyWithAuthHeader('POST', args, next, access_token) + ) + const res = await getResponseMetadata(result) + return res +}