Skip to content

Commit 560a49c

Browse files
committed
feat(org): read each member's own mail and calendar for Home
Adds the session-only email and calendar reads behind Home's widgets, the single-source removal from search history, and moves the member integrations inventory and the account query keys where a server prefetch can reach them.
1 parent 3019c8e commit 560a49c

34 files changed

Lines changed: 2024 additions & 335 deletions
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { getOrganizationHomeCalendarContract } from '@/lib/api/contracts/organization-home'
2+
import {
3+
defineInternalJsonRoute,
4+
internalRateLimits,
5+
internalSessionAuth,
6+
} from '@/lib/api/server/routes'
7+
import {
8+
organizationHomeOperations,
9+
readOrganizationHomeCalendar,
10+
} from '@/lib/organizations/application/home-widgets'
11+
import { createCredentialGroupInternalErrorPolicy } from '@/app/api/workspaces/[id]/credential-groups/error-policy'
12+
13+
export const GET = defineInternalJsonRoute({
14+
contract: getOrganizationHomeCalendarContract,
15+
auth: internalSessionAuth,
16+
operation: organizationHomeOperations.readCalendar,
17+
rateLimit: internalRateLimits.user({ bucketName: 'organization-home-calendar' }),
18+
errorPolicy: createCredentialGroupInternalErrorPolicy(
19+
'Failed to load calendar',
20+
'Organization not found'
21+
),
22+
mapInput: ({ params, query }) => ({ organizationId: params.id, ...query }),
23+
useCase: readOrganizationHomeCalendar,
24+
staticResponseHeaders: { 'Cache-Control': 'private, no-store' },
25+
})
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { getOrganizationHomeEmailContract } from '@/lib/api/contracts/organization-home'
2+
import {
3+
defineInternalJsonRoute,
4+
internalRateLimits,
5+
internalSessionAuth,
6+
} from '@/lib/api/server/routes'
7+
import {
8+
organizationHomeOperations,
9+
readOrganizationHomeEmail,
10+
} from '@/lib/organizations/application/home-widgets'
11+
import { createCredentialGroupInternalErrorPolicy } from '@/app/api/workspaces/[id]/credential-groups/error-policy'
12+
13+
export const GET = defineInternalJsonRoute({
14+
contract: getOrganizationHomeEmailContract,
15+
auth: internalSessionAuth,
16+
operation: organizationHomeOperations.readEmail,
17+
rateLimit: internalRateLimits.user({ bucketName: 'organization-home-email' }),
18+
errorPolicy: createCredentialGroupInternalErrorPolicy(
19+
'Failed to load email',
20+
'Organization not found'
21+
),
22+
mapInput: ({ params }) => ({ organizationId: params.id }),
23+
useCase: readOrganizationHomeEmail,
24+
staticResponseHeaders: { 'Cache-Control': 'private, no-store' },
25+
})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { removeSearchHistorySourceContract } from '@/lib/api/contracts/knowledge/search-history'
2+
import {
3+
defineInternalJsonRoute,
4+
internalOrchestrationErrorPolicy,
5+
internalRateLimits,
6+
internalSessionAuth,
7+
} from '@/lib/api/server/routes'
8+
import {
9+
removeSearchHistorySource,
10+
searchHistoryOperations,
11+
} from '@/lib/knowledge/application/search-history'
12+
13+
export const DELETE = defineInternalJsonRoute({
14+
contract: removeSearchHistorySourceContract,
15+
auth: internalSessionAuth,
16+
operation: searchHistoryOperations.removeSource,
17+
rateLimit: internalRateLimits.user({ bucketName: 'search-history-write' }),
18+
errorPolicy: internalOrchestrationErrorPolicy,
19+
parseOptions: { maxBodyBytes: 24 * 1024 },
20+
mapInput: ({ params, body }) => ({ organizationId: params.id, url: body.url }),
21+
useCase: removeSearchHistorySource,
22+
staticResponseHeaders: { 'Cache-Control': 'private, no-store' },
23+
})

‎apps/sim/app/o/[organizationId]/home/components/get-started/get-started.tsx‎

Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,8 @@ import { ArrowRight } from '@sim/emcn/icons'
66
import { HomeSection } from '@/components/home/home-section'
77
import { SettingsGuardedLink } from '@/components/settings/settings-guarded-link'
88
import { OAUTH_SEARCH_READ_SCOPE, oauthScopeSatisfies } from '@/lib/auth/oauth-provider'
9+
import { hasSearchIntegrationReady } from '@/lib/credential-groups/member-integrations'
910
import { organizationRoutes } from '@/lib/navigation/paths'
10-
import {
11-
liveSearchProviderForCredential,
12-
supportsLiveSearchMode,
13-
} from '@/lib/sim-search/live/provider-catalog'
1411
import { useOrganizationContext } from '@/app/o/[organizationId]/providers/organization-provider'
1512
import { useAuthorizedApps } from '@/hooks/queries/oauth-provider'
1613
import { useOrganizationAccounts } from '@/hooks/queries/organization-accounts'
@@ -94,63 +91,14 @@ export function GetStarted() {
9491
authorizedApps?.pages.some((page) =>
9592
page.apps.some((app) => oauthScopeSatisfies(app.scopes, OAUTH_SEARCH_READ_SCOPE))
9693
) ?? false
97-
const approvedProviders = new Set(
98-
integrations
99-
?.filter((integration) => integration.approved && integration.available !== false)
100-
.map((integration) => integration.connectorType)
101-
)
102-
const readyOptions = new Set(
103-
accounts?.credentialGroup?.options
104-
.filter((option) => {
105-
const provider = liveSearchProviderForCredential(option.provider)
106-
return (
107-
option.status === 'active' &&
108-
option.configurationStatus === 'ready' &&
109-
provider &&
110-
supportsLiveSearchMode(provider, 'member') &&
111-
approvedProviders.has(provider)
112-
)
113-
})
114-
.map((option) => option.id)
115-
)
116-
const readyMcpServers = new Set(
117-
accounts?.credentialGroup?.mcpServers
118-
.filter((server) => {
119-
const provider = liveSearchProviderForCredential(`mcp:${server.managedConnectorId}`)
120-
return (
121-
server.enabled &&
122-
provider &&
123-
approvedProviders.has(provider) &&
124-
accounts.availableMcpConnectors.some((id) => id === server.managedConnectorId)
125-
)
126-
})
127-
.map((server) => server.id)
128-
)
129-
const hasSearchConnection =
130-
accounts?.credentialGroup?.status === 'active' &&
131-
(accounts.viewerAccounts?.some(
132-
(account) => account.status === 'active' && readyOptions.has(account.optionId)
133-
) ||
134-
accounts.viewerMcpAccounts?.some(
135-
(account) => account.status === 'active' && readyMcpServers.has(account.mcpServerId)
136-
))
137-
13894
const hrefs: Record<StepId, string> = {
13995
'connect-integration': viewer.isAdmin
14096
? routes.settingsSection('integrations')
14197
: routes.integrations,
14298
'connect-sim-search': routes.settingsSection('search-mcp'),
14399
}
144100
const completed: Record<StepId, boolean> = {
145-
'connect-integration': Boolean(
146-
hasSearchConnection ||
147-
integrations?.some(
148-
(integration) =>
149-
integration.approved &&
150-
integration.available !== false &&
151-
integration.configuredServiceSource
152-
)
153-
),
101+
'connect-integration': hasSearchIntegrationReady(accounts, integrations),
154102
'connect-sim-search': hasSearchAuthorization,
155103
}
156104
const steps = STEPS.filter((step) =>

‎apps/sim/app/o/[organizationId]/integrations/assistant-account-integrations.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { ChipSelect, toast } from '@sim/emcn'
44
import type { SearchIntegrationStatus } from '@/lib/api/contracts/knowledge/search-integrations'
55
import type { OrganizationAccountsSettings } from '@/lib/api/contracts/organization-accounts'
66
import { assistantSearchSourceForProvider } from '@/lib/assistant-actions/providers'
7+
import { assistantAccountOptions } from '@/lib/credential-groups/member-integrations'
78
import {
89
getCredentialGroupProviderId,
910
getCredentialGroupProviderService,
1011
} from '@/lib/credential-groups/providers'
1112
import { AccountActionsMenu } from '@/app/o/[organizationId]/integrations/account-actions-menu'
12-
import { assistantAccountOptions } from '@/app/o/[organizationId]/integrations/utils'
1313
import { IntegrationTile } from '@/app/workspace/[workspaceId]/integrations/components/integrations-showcase'
1414
import { SettingsResourceRow } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'
1515
import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section'

‎apps/sim/app/o/[organizationId]/integrations/integration-detail.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import type { ReactNode } from 'react'
44
import { toast } from '@sim/emcn'
5+
import type { MemberIntegration } from '@/lib/credential-groups/member-integrations'
56
import { AccountConnectionActions } from '@/app/o/[organizationId]/integrations/account-connection-actions'
67
import { AccountToolPermissionsContent } from '@/app/o/[organizationId]/integrations/accounts/[credentialId]/tool-permissions'
7-
import type { MemberIntegration } from '@/app/o/[organizationId]/integrations/utils'
88
import {
99
CredentialDetailHeading,
1010
CredentialDetailLayout,

‎apps/sim/app/o/[organizationId]/integrations/live-member-integrations.tsx‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,15 @@ import type { ReactNode } from 'react'
44
import { ChipLink } from '@sim/emcn'
55
import { ArrowLeft } from '@sim/emcn/icons'
66
import { useQueryStates } from 'nuqs'
7+
import { memberIntegrationEntries } from '@/lib/credential-groups/member-integrations'
78
import { organizationRoutes } from '@/lib/navigation/paths'
89
import { OrganizationPage } from '@/app/o/[organizationId]/components/organization-page'
910
import { IntegrationDetail } from '@/app/o/[organizationId]/integrations/integration-detail'
1011
import {
1112
integrationViewParsers,
1213
serializeIntegrationView,
1314
} from '@/app/o/[organizationId]/integrations/search-params'
14-
import {
15-
memberIntegrationEntries,
16-
memberIntegrationStatus,
17-
} from '@/app/o/[organizationId]/integrations/utils'
15+
import { memberIntegrationStatus } from '@/app/o/[organizationId]/integrations/utils'
1816
import {
1917
GenericSecretSourceIcon,
2018
GenericSecretSourceRow,

0 commit comments

Comments
 (0)