diff --git a/surfsense_backend/app/users.py b/surfsense_backend/app/users.py index d51b30bd7..dd284307f 100644 --- a/surfsense_backend/app/users.py +++ b/surfsense_backend/app/users.py @@ -1,3 +1,4 @@ +import logging import uuid from fastapi import Depends, Request, Response @@ -12,7 +13,17 @@ from fastapi_users.db import SQLAlchemyUserDatabase from pydantic import BaseModel from app.config import config -from app.db import User, get_user_db +from app.db import ( + SearchSpace, + SearchSpaceMembership, + SearchSpaceRole, + User, + async_session_maker, + get_default_roles_config, + get_user_db, +) + +logger = logging.getLogger(__name__) class BearerResponse(BaseModel): @@ -36,7 +47,59 @@ class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]): verification_token_secret = SECRET async def on_after_register(self, user: User, request: Request | None = None): - print(f"User {user.id} has registered.") + """ + Called after a user registers. Creates a default search space for the user + so they can start chatting immediately without manual setup. + """ + logger.info(f"User {user.id} has registered. Creating default search space...") + + try: + async with async_session_maker() as session: + # Create default search space + default_search_space = SearchSpace( + name="My Search Space", + description="Your personal search space", + user_id=user.id, + ) + session.add(default_search_space) + await session.flush() # Get the search space ID + + # Create default roles + default_roles = get_default_roles_config() + owner_role_id = None + + for role_config in default_roles: + db_role = SearchSpaceRole( + name=role_config["name"], + description=role_config["description"], + permissions=role_config["permissions"], + is_default=role_config["is_default"], + is_system_role=role_config["is_system_role"], + search_space_id=default_search_space.id, + ) + session.add(db_role) + await session.flush() + + if role_config["name"] == "Owner": + owner_role_id = db_role.id + + # Create owner membership + owner_membership = SearchSpaceMembership( + user_id=user.id, + search_space_id=default_search_space.id, + role_id=owner_role_id, + is_owner=True, + ) + session.add(owner_membership) + + await session.commit() + logger.info( + f"Created default search space (ID: {default_search_space.id}) for user {user.id}" + ) + except Exception as e: + logger.error( + f"Failed to create default search space for user {user.id}: {e}" + ) async def on_after_forgot_password( self, user: User, token: str, request: Request | None = None diff --git a/surfsense_web/app/dashboard/page.tsx b/surfsense_web/app/dashboard/page.tsx index fa611aabf..ad1c6ad9d 100644 --- a/surfsense_web/app/dashboard/page.tsx +++ b/surfsense_web/app/dashboard/page.tsx @@ -7,6 +7,7 @@ import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; +import { useEffect } from "react"; import { deleteSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms"; import { searchSpacesAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { currentUserAtom } from "@/atoms/user/user-query.atoms"; @@ -129,6 +130,7 @@ const ErrorScreen = ({ message }: { message: string }) => { const DashboardPage = () => { const t = useTranslations("dashboard"); const tCommon = useTranslations("common"); + const router = useRouter(); // Animation variants const containerVariants: Variants = { @@ -164,6 +166,15 @@ const DashboardPage = () => { const { data: user, isPending: isLoadingUser, error: userError } = useAtomValue(currentUserAtom); + // Auto-redirect to chat for users with exactly 1 search space + useEffect(() => { + if (loading) return; + + if (searchSpaces.length === 1) { + router.replace(`/dashboard/${searchSpaces[0].id}/new-chat`); + } + }, [loading, searchSpaces, router]); + // Create user object for UserDropdown const customUser = { name: user?.email ? user.email.split("@")[0] : "User", @@ -173,7 +184,8 @@ const DashboardPage = () => { avatar: "/icon-128.svg", // Default avatar }; - if (loading) return ; + // Show loading while loading or auto-redirecting (single search space) + if (loading || (searchSpaces.length === 1 && !error)) return ; if (error) return ; const handleDeleteSearchSpace = async (id: number) => { diff --git a/surfsense_web/components/Logo.tsx b/surfsense_web/components/Logo.tsx index 79799942b..58f8d1c9f 100644 --- a/surfsense_web/components/Logo.tsx +++ b/surfsense_web/components/Logo.tsx @@ -7,7 +7,13 @@ import { cn } from "@/lib/utils"; export const Logo = ({ className }: { className?: string }) => { return ( - logo + logo ); }; diff --git a/surfsense_web/components/TokenHandler.tsx b/surfsense_web/components/TokenHandler.tsx index 42905ac0d..b4ca36298 100644 --- a/surfsense_web/components/TokenHandler.tsx +++ b/surfsense_web/components/TokenHandler.tsx @@ -1,6 +1,6 @@ "use client"; -import { useRouter, useSearchParams } from "next/navigation"; +import { useSearchParams } from "next/navigation"; import { useEffect } from "react"; import { getAndClearRedirectPath, setBearerToken } from "@/lib/auth-utils"; import { trackLoginSuccess } from "@/lib/posthog/events"; @@ -25,7 +25,6 @@ const TokenHandler = ({ tokenParamName = "token", storageKey = "surfsense_bearer_token", }: TokenHandlerProps) => { - const router = useRouter(); const searchParams = useSearchParams(); useEffect(() => { @@ -58,14 +57,14 @@ const TokenHandler = ({ const finalRedirectPath = savedRedirectPath || redirectPath; // Redirect to the appropriate path - router.push(finalRedirectPath); + window.location.href = finalRedirectPath; } catch (error) { console.error("Error storing token in localStorage:", error); // Even if there's an error, try to redirect to the default path - router.push(redirectPath); + window.location.href = redirectPath; } } - }, [searchParams, tokenParamName, storageKey, redirectPath, router]); + }, [searchParams, tokenParamName, storageKey, redirectPath]); return (
diff --git a/surfsense_web/components/UserDropdown.tsx b/surfsense_web/components/UserDropdown.tsx index 966193c7f..a7f9c89ac 100644 --- a/surfsense_web/components/UserDropdown.tsx +++ b/surfsense_web/components/UserDropdown.tsx @@ -34,14 +34,14 @@ export function UserDropdown({ if (typeof window !== "undefined") { localStorage.removeItem("surfsense_bearer_token"); - router.push("/"); + window.location.href = "/"; } } catch (error) { console.error("Error during logout:", error); // Optionally, provide user feedback if (typeof window !== "undefined") { alert("Logout failed. Please try again."); - router.push("/"); + window.location.href = "/"; } } };