Skip to content

Commit b5d4201

Browse files
authored
Merge pull request #3530 from Agenta-AI/release/v0.80.6
[release] v0.80.6
2 parents e5916c4 + 8578e49 commit b5d4201

File tree

12 files changed

+152
-34
lines changed

12 files changed

+152
-34
lines changed

api/oss/databases/postgres/migrations/core/versions/a2b3c4d5e6f7_add_retention_helper_indexes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Add retention helper indexes on projects
22
33
Revision ID: a2b3c4d5e6f7
4-
Revises: a2b3c4d5e6f7
4+
Revises: c3b2a1d4e5f6
55
Create Date: 2025-01-06 12:00:00.000000
66
77
"""
@@ -13,7 +13,7 @@
1313

1414
# revision identifiers, used by Alembic.
1515
revision: str = "a2b3c4d5e6f7"
16-
down_revision: Union[str, None] = "a2b3c4d5e6f7"
16+
down_revision: Union[str, None] = "c3b2a1d4e5f6"
1717
branch_labels: Union[str, Sequence[str], None] = None
1818
depends_on: Union[str, Sequence[str], None] = None
1919

api/oss/src/core/auth/supertokens/overrides.py

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,10 @@
5555
from oss.src.services.exceptions import UnauthorizedException
5656
from oss.src.services.db_manager import (
5757
get_user_with_email,
58-
check_if_user_exists_and_create_organization,
5958
check_if_user_invitation_exists,
59+
is_first_user_signup,
60+
get_oss_organization,
61+
setup_oss_organization_for_first_user,
6062
)
6163

6264
log = get_module_logger(__name__)
@@ -236,28 +238,53 @@ async def _create_account(email: str, uid: str) -> bool:
236238
"email": email,
237239
}
238240

239-
# For OSS: compute organization before calling create_accounts
240241
# For EE: organization is created inside create_accounts
242+
# For OSS: we need to handle first user specially to avoid FK violation
241243
if is_ee():
242244
await create_accounts(payload)
243245
else:
244-
# OSS: Compute or get the single organization
245-
organization_db = await check_if_user_exists_and_create_organization(
246-
user_email=email
247-
)
246+
# OSS: Check if this is the first user signup
247+
first_user = await is_first_user_signup()
248+
249+
if first_user:
250+
# First user: Create user first, then organization
251+
# This avoids the FK violation where org.owner_id references non-existent user
252+
user_db = await create_accounts(payload)
253+
254+
# Now create organization with the real user ID
255+
organization_db = await setup_oss_organization_for_first_user(
256+
user_id=user_db.id,
257+
user_email=email,
258+
)
248259

249-
# Verify user can join (invitation check)
250-
user_invitation_exists = await check_if_user_invitation_exists(
251-
email=email,
252-
organization_id=str(organization_db.id),
253-
)
254-
if not user_invitation_exists:
255-
raise UnauthorizedException(
256-
detail="You need to be invited by the organization owner to gain access."
260+
# Assign user to organization
261+
from oss.src.services.db_manager import _assign_user_to_organization_oss
262+
263+
await _assign_user_to_organization_oss(
264+
user_db=user_db,
265+
organization_id=str(organization_db.id),
266+
email=email,
257267
)
268+
else:
269+
# Not first user: Get existing organization and check invitation
270+
organization_db = await get_oss_organization()
271+
if not organization_db:
272+
raise UnauthorizedException(
273+
detail="No organization found. Please contact the administrator."
274+
)
258275

259-
payload["organization_id"] = str(organization_db.id)
260-
await create_accounts(payload)
276+
# Verify user can join (invitation check)
277+
user_invitation_exists = await check_if_user_invitation_exists(
278+
email=email,
279+
organization_id=str(organization_db.id),
280+
)
281+
if not user_invitation_exists:
282+
raise UnauthorizedException(
283+
detail="You need to be invited by the organization owner to gain access."
284+
)
285+
286+
payload["organization_id"] = str(organization_db.id)
287+
await create_accounts(payload)
261288

262289
if env.posthog.enabled and env.posthog.api_key:
263290
try:

api/oss/src/services/db_manager.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1060,8 +1060,74 @@ async def get_user(user_uid: str) -> UserDB:
10601060
return user
10611061

10621062

1063+
async def is_first_user_signup() -> bool:
1064+
"""Check if this is the first user signing up (no users exist yet)."""
1065+
async with engine.core_session() as session:
1066+
total_users = (
1067+
await session.scalar(select(func.count()).select_from(UserDB)) or 0
1068+
)
1069+
return total_users == 0
1070+
1071+
1072+
async def get_oss_organization() -> Optional[OrganizationDB]:
1073+
"""Get the single OSS organization if it exists."""
1074+
organizations_db = await get_organizations()
1075+
if organizations_db:
1076+
return organizations_db[0]
1077+
return None
1078+
1079+
1080+
async def setup_oss_organization_for_first_user(
1081+
user_id: uuid.UUID,
1082+
user_email: str,
1083+
) -> OrganizationDB:
1084+
"""
1085+
Setup the OSS organization for the first user.
1086+
1087+
This should only be called after the user has been created.
1088+
1089+
Args:
1090+
user_id: The UUID of the newly created user
1091+
user_email: The email of the user (for analytics)
1092+
1093+
Returns:
1094+
OrganizationDB: The created organization
1095+
"""
1096+
organization_db = await create_organization(
1097+
name="Organization",
1098+
owner_id=user_id,
1099+
created_by_id=user_id,
1100+
)
1101+
workspace_db = await create_workspace(
1102+
name="Default",
1103+
organization_id=str(organization_db.id),
1104+
)
1105+
1106+
# update default project with organization and workspace ids
1107+
await create_or_update_default_project(
1108+
values_to_update={
1109+
"organization_id": organization_db.id,
1110+
"workspace_id": workspace_db.id,
1111+
"project_name": "Default",
1112+
}
1113+
)
1114+
1115+
analytics_service.capture_oss_deployment_created(
1116+
user_email=user_email,
1117+
organization_id=str(organization_db.id),
1118+
)
1119+
1120+
return organization_db
1121+
1122+
10631123
async def check_if_user_exists_and_create_organization(user_email: str):
1064-
"""Check if a user with the given email exists and if not, create a new organization for them."""
1124+
"""
1125+
Check if a user with the given email exists and if not, create a new organization for them.
1126+
1127+
DEPRECATED: This function has a bug where it creates an organization before the user exists,
1128+
causing FK violations. Use is_first_user_signup() + setup_oss_organization_for_first_user() instead.
1129+
Kept for backward compatibility but should not be called for new signups.
1130+
"""
10651131

10661132
async with engine.core_session() as session:
10671133
user_query = await session.execute(select(UserDB).filter_by(email=user_email))

api/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "api"
3-
version = "0.80.5"
3+
version = "0.80.6"
44
description = "Agenta API"
55
authors = [
66
{ name = "Mahmoud Mabrouk", email = "[email protected]" },

sdk/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "agenta"
3-
version = "0.80.5"
3+
version = "0.80.6"
44
description = "The SDK for agenta is an open-source LLMOps platform."
55
readme = "README.md"
66
authors = [

services/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "services"
3-
version = "0.80.5"
3+
version = "0.80.6"
44
description = "Agenta Services (Chat & Completion)"
55
authors = [
66
"Mahmoud Mabrouk <[email protected]>",

web/ee/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@agenta/ee",
3-
"version": "0.80.5",
3+
"version": "0.80.6",
44
"private": true,
55
"engines": {
66
"node": ">=18"

web/oss/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@agenta/oss",
3-
"version": "0.80.5",
3+
"version": "0.80.6",
44
"private": true,
55
"engines": {
66
"node": ">=18"

web/oss/src/hooks/usePostAuthRedirect.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,22 @@ const usePostAuthRedirect = () => {
9595
if (isInvitedUser) {
9696
console.log("[post-auth] redirect invited new user -> /workspaces/accept")
9797
await router.push("/workspaces/accept?survey=true")
98-
} else {
98+
} else if (isEE()) {
99+
// EE: Show post-signup survey/onboarding form
99100
console.log("[post-auth] redirect new user -> /post-signup")
100101
writePostSignupPending()
101102
await resetAuthState()
102103
setIsNewUser(true)
103104
await router.push("/post-signup")
105+
} else {
106+
// OSS: Skip post-signup, go directly to workspace
107+
console.log("[post-auth] OSS new user, skipping post-signup")
108+
setIsNewUser(true)
109+
// Fall through to normal workspace redirect logic below
110+
}
111+
if (isInvitedUser || isEE()) {
112+
return
104113
}
105-
return
106114
}
107115

108116
if (isInvitedUser) {

web/oss/src/lib/helpers/analytics/hooks/usePostHogAg.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ export const usePostHogAg = (): ExtendedPostHog | null => {
2323
const analyticsId = isDemo() && user?.email ? user.email : baseDistinctId
2424
const identifiedRef = useRef<string | null>(null)
2525
const aliasedRef = useRef(false)
26+
27+
const personProps = useMemo(() => {
28+
if (!user?.email) return null
29+
30+
const props: Record<string, unknown> = {email: user.email}
31+
if (user.username) {
32+
props.username = user.username
33+
}
34+
return props
35+
}, [user?.email, user?.username])
2636
const baseCapture = posthog?.capture?.bind(posthog)
2737
const baseIdentify = posthog?.identify?.bind(posthog)
2838
const capture: PostHog["capture"] = useCallback(
@@ -64,8 +74,12 @@ export const usePostHogAg = (): ExtendedPostHog | null => {
6474
aliasedRef.current = true
6575
}
6676
identifiedRef.current = analyticsId
67-
identify(analyticsId)
68-
}, [analyticsId, baseDistinctId, identify, posthog, user?.email])
77+
if (personProps) {
78+
identify(analyticsId, personProps)
79+
} else {
80+
identify(analyticsId)
81+
}
82+
}, [analyticsId, baseDistinctId, identify, personProps, posthog, user?.email])
6983

7084
if (!posthog) return null
7185
return Object.assign(posthog, {identify, capture}) as ExtendedPostHog

0 commit comments

Comments
 (0)