From 9017150dc4e84be131a11fd6e5326b8fcc79bc32 Mon Sep 17 00:00:00 2001 From: Ahmet Kilinc Date: Thu, 17 Jul 2025 16:14:07 +0100 Subject: [PATCH] lots of refactoring and api fixes --- .../(routes)/settings/organization/page.tsx | 468 +--- .../organization/create-organization.tsx | 195 ++ .../components/organization/invite-member.tsx | 99 + .../organization/my-organizations.tsx | 120 + .../organization/pending-invites.tsx | 84 + .../db/migrations/0042_wealthy_earthquake.sql | 2 + .../src/db/migrations/meta/0042_snapshot.json | 2124 +++++++++++++++++ .../src/db/migrations/meta/_journal.json | 9 +- apps/server/src/db/schema.ts | 1 + apps/server/src/trpc/routes/organization.ts | 51 + 10 files changed, 2723 insertions(+), 430 deletions(-) create mode 100644 apps/mail/components/organization/create-organization.tsx create mode 100644 apps/mail/components/organization/invite-member.tsx create mode 100644 apps/mail/components/organization/my-organizations.tsx create mode 100644 apps/mail/components/organization/pending-invites.tsx create mode 100644 apps/server/src/db/migrations/0042_wealthy_earthquake.sql create mode 100644 apps/server/src/db/migrations/meta/0042_snapshot.json diff --git a/apps/mail/app/(routes)/settings/organization/page.tsx b/apps/mail/app/(routes)/settings/organization/page.tsx index 209bfb65c..0e37298de 100644 --- a/apps/mail/app/(routes)/settings/organization/page.tsx +++ b/apps/mail/app/(routes)/settings/organization/page.tsx @@ -1,29 +1,19 @@ -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import CreateOrganization from '@/components/organization/create-organization'; import { SettingsEditor } from '@/components/organization/settings-editor'; -import { Building2, Loader2, Mail, UserPlus, Users } from 'lucide-react'; +import MyOrganizations from '@/components/organization/my-organizations'; +import PendingInvites from '@/components/organization/pending-invites'; import { TeamManager } from '@/components/organization/team-manager'; +import InviteMember from '@/components/organization/invite-member'; import { MemberList } from '@/components/organization/member-list'; import { SettingsCard } from '@/components/settings/settings-card'; import { useMutation, useQuery } from '@tanstack/react-query'; import { useTRPC } from '@/providers/query-provider'; -import { Button } from '@/components/ui/button'; import { authClient } from '@/lib/auth-client'; -import { Label } from '@/components/ui/label'; -import { Input } from '@/components/ui/input'; -import { Badge } from '@/components/ui/badge'; import { useEffect, useState } from 'react'; import { toast } from 'sonner'; -type Role = 'member' | 'admin' | 'owner'; - type Domain = { domain: string; verified: boolean; @@ -31,28 +21,37 @@ type Domain = { }; export default function OrganizationPage() { - const [loading, setLoading] = useState(false); - const [inviteEmail, setInviteEmail] = useState(''); - const [inviteRole, setInviteRole] = useState('member'); - const [orgName, setOrgName] = useState(''); - const [orgSlug, setOrgSlug] = useState(''); - const [orgDomain, setOrgDomain] = useState(''); const [organizations, setOrganizations] = useState([]); const [activeOrg, setActiveOrg] = useState(null); const [domains, setDomains] = useState([]); const [newDomain, setNewDomain] = useState(''); - // Invitations state - const [invites, setInvites] = useState([]); - const [loadingInvites, setLoadingInvites] = useState(false); const [verifying, setVerifying] = useState(false); const [verifyMsg, setVerifyMsg] = useState(null); - const [domainVerificationToken, setDomainVerificationToken] = useState(null); - const [domainVerified, setDomainVerified] = useState(false); - const [creatingOrg, setCreatingOrg] = useState(false); - const [verificationToken, setVerificationToken] = useState(null); const trpc = useTRPC(); + const { data: activeOrganizationId, refetch: refetchActiveOrganizationId } = useQuery({ + ...trpc.organization.getUsersActiveOrganizationId.queryOptions(), + }); + + const setActiveOrganizationMutation = useMutation( + trpc.organization.setActiveOrganization.mutationOptions({ + onSuccess: () => { + toast.success('Active organization set successfully'); + }, + onError: (error) => { + toast.error(`Failed to set active organization: ${error.message}`); + }, + }), + ); + + const handleSetActiveOrganization = async (org: any) => { + await setActiveOrganizationMutation.mutateAsync({ + organizationId: org.id, + }); + refetchActiveOrganizationId(); + }; + // TRPC queries const { data: domainsData, refetch: refetchDomains } = useQuery({ ...trpc.organization.listDomains.queryOptions({ organizationId: activeOrg?.id || '' }), @@ -60,7 +59,6 @@ export default function OrganizationPage() { }); // TRPC mutations - const verifyDomainMutation = useMutation(trpc.organization.verifyDomain.mutationOptions()); const addDomainMutation = useMutation(trpc.organization.addDomain.mutationOptions()); const removeDomainMutation = useMutation(trpc.organization.removeDomain.mutationOptions()); const verifyDomainForOrgMutation = useMutation( @@ -79,77 +77,6 @@ export default function OrganizationPage() { } }, [domainsData]); - // Test organization creation - const handleCreateOrganization = async () => { - if (!orgName || !orgSlug || !orgDomain || !domainVerified) { - toast.error('Please fill in all fields and verify your domain'); - return; - } - setCreatingOrg(true); - try { - await authClient.organization.create({ - name: orgName, - slug: orgSlug, - }); - toast.success(`Organization "${orgName}" created successfully!`); - setOrgName(''); - setOrgSlug(''); - setOrgDomain(''); - setDomainVerificationToken(null); - setDomainVerified(false); - setVerifyMsg(null); - setVerificationToken(null); - // Refresh organizations list - const orgs = await authClient.organization.list(); - setOrganizations(orgs.data || []); - } catch (error: any) { - toast.error(`Failed to create organization: ${error.message}`); - } finally { - setCreatingOrg(false); - } - }; - - // Test member invitation - const handleInviteMember = async () => { - if (!inviteEmail || !activeOrg) { - toast.error('Please enter an email and select an organization'); - return; - } - - setLoading(true); - try { - await authClient.organization.inviteMember({ - email: inviteEmail, - role: inviteRole, - organizationId: activeOrg.id, - }); - - toast.success(`Invitation sent to ${inviteEmail}!`); - setInviteEmail(''); - } catch (error: any) { - toast.error(`Failed to send invitation: ${error.message}`); - } finally { - setLoading(false); - } - }; - - // Test setting active organization - const handleSetActiveOrg = async (org: any) => { - setLoading(true); - try { - await authClient.organization.setActive({ - organizationId: org.id, - }); - - setActiveOrg(org); - toast.success(`Active organization set to "${org.name}"`); - } catch (error: any) { - toast.error(`Failed to set active organization: ${error.message}`); - } finally { - setLoading(false); - } - }; - async function addDomain(e?: React.FormEvent) { if (e) e.preventDefault(); if (!newDomain || !activeOrg?.id) return; @@ -210,113 +137,23 @@ export default function OrganizationPage() { } } - // Fetch pending invitations - TODO: Convert to TRPC when invitation router is available - async function fetchInvites() { - if (!activeOrg?.id) return; - setLoadingInvites(true); - try { - const res = await fetch( - `${import.meta.env.VITE_PUBLIC_BACKEND_URL}/api/invitations?organizationId=${activeOrg.id}&status=pending`, - { credentials: 'include' }, - ); - const data = (await res.json()) as { invitations: any[] }; - setInvites(data.invitations || []); - } catch (error) { - console.error('Failed to fetch invitations:', error); - } finally { - setLoadingInvites(false); - } - } - - // Cancel an invitation - TODO: Convert to TRPC when invitation router is available - async function cancelInvite(inviteId: string) { - if (!inviteId) return; - try { - await fetch(`${import.meta.env.VITE_PUBLIC_BACKEND_URL}/api/invitations/${inviteId}`, { - method: 'DELETE', - credentials: 'include', - }); - toast.success('Invitation cancelled'); - fetchInvites(); - } catch (error: any) { - toast.error(`Failed to cancel invite: ${error.message}`); - } - } - - // Fetch invitations when org changes - useEffect(() => { - if (activeOrg?.id) fetchInvites(); - }, [activeOrg?.id]); - // Load organizations on mount useEffect(() => { const loadOrganizations = async () => { try { const orgs = await authClient.organization.list(); + const activeOrg = orgs.data?.find( + (org) => org.id === activeOrganizationId?.activeOrganizationId, + ); + setActiveOrg(activeOrg); setOrganizations(orgs.data || []); - - // Set first org as active if available - if (orgs.data && orgs.data.length > 0) { - setActiveOrg(orgs.data[0]); - } } catch (error) { console.error('Failed to load organizations:', error); } }; loadOrganizations(); - }, []); - - // Check domain verification and slug availability - const handleAddDomain = async () => { - if (!orgName || !orgSlug || !orgDomain) { - toast.error('Please fill in organization name, slug, and domain'); - return; - } - setVerifying(true); - setVerifyMsg(null); - - try { - // Check if slug is available - const orgs = await authClient.organization.list(); - const slugExists = orgs.data?.some((org) => org.slug === orgSlug); - if (slugExists) { - toast.error('Organization slug already exists. Please choose a different one.'); - setVerifying(false); - return; - } - - // Check domain verification directly - const result = await verifyDomainMutation.mutateAsync({ - domain: orgDomain, - verificationToken: verificationToken ?? undefined, - }); - - // Store the verification token for reuse - if (result.verificationToken) { - setVerificationToken(result.verificationToken); - } - - if (result.verified) { - setDomainVerified(true); - setVerifyMsg('Domain verified! You can now create your organization.'); - } else { - setVerifyMsg( - result.message || 'Domain verification failed. Please add the TXT record to your DNS.', - ); - } - } catch (error: any) { - toast.error(`Failed to verify domain: ${error.message}`); - } finally { - setVerifying(false); - } - }; - - // DEBUG BYPASS VERIFICATION - const handleDebugBypass = () => { - setDomainVerified(true); - toast.success('DEBUG: Domain verification bypassed!'); - }; + }, [activeOrganizationId]); return (
@@ -343,251 +180,24 @@ export default function OrganizationPage() { {organizations.length > 0 && ( - - - - - Your Organizations - - Select an organization to manage members - - -
- {organizations.map((org) => ( -
handleSetActiveOrg(org)} - > -
- {org.logo && ( - {`${org.name} { - (e.currentTarget as HTMLImageElement).style.display = 'none'; - }} - /> - )} -
-

{org.name}

-

@{org.slug}

-
- {activeOrg?.id === org.id && Active} -
- {org.member?.role || 'Unknown'} -
- ))} -
-
-
+ )}
- - - - - Create Organization - - - Create a new organization. You must verify your domain before proceeding. - - - -
-
- - setOrgName(e.target.value)} - placeholder="My Organization" - /> -
-
- - setOrgSlug(e.target.value)} - placeholder="my-org" - /> -
-
- - setOrgDomain(e.target.value)} - placeholder="example.com" - /> - -
-
- {verifyMsg && ( -
-
{verifyMsg}
- {verificationToken && !domainVerified && ( -
-
Add this TXT record to your DNS:
- - zero-verification={verificationToken} - - -
- )} - -
- )} - -
-
+
- {activeOrg && ( - - - - - Invite Member - - Invite a new member to "{activeOrg.name}" - - -
-
- - setInviteEmail(e.target.value)} - placeholder="member@example.com" - /> -
-
- - -
-
- -
-
- )} + {activeOrg && }
- {activeOrg && ( - - - - - Pending Invitations - - - Manage outgoing invitations for "{activeOrg.name}" - - - - {loadingInvites ? ( -
- -
- ) : invites.length > 0 ? ( - invites.map((invite) => ( -
-
-

{invite.email}

-

{invite.role}

-
- -
- )) - ) : ( -

No pending invitations.

- )} -
-
- )} + {activeOrg && }
diff --git a/apps/mail/components/organization/create-organization.tsx b/apps/mail/components/organization/create-organization.tsx new file mode 100644 index 000000000..2b95bcd64 --- /dev/null +++ b/apps/mail/components/organization/create-organization.tsx @@ -0,0 +1,195 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { useTRPC } from '@/providers/query-provider'; +import { useMutation } from '@tanstack/react-query'; +import { Building2, Loader2 } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { authClient } from '@/lib/auth-client'; +import { Label } from '@/components/ui/label'; +import { Input } from '@/components/ui/input'; +import { useState } from 'react'; +import { toast } from 'sonner'; + +export default function CreateOrganization() { + const [orgName, setOrgName] = useState(''); + const [orgSlug, setOrgSlug] = useState(''); + const [orgDomain, setOrgDomain] = useState(''); + const [creatingOrg, setCreatingOrg] = useState(false); + const [verifying, setVerifying] = useState(false); + const [verifyMsg, setVerifyMsg] = useState(null); + const [verificationToken, setVerificationToken] = useState(null); + const [domainVerified, setDomainVerified] = useState(false); + const [domainVerificationToken, setDomainVerificationToken] = useState(null); + const [organizations, setOrganizations] = useState([]); + + const trpc = useTRPC(); + + const verifyDomainMutation = useMutation(trpc.organization.verifyDomain.mutationOptions()); + + // Test organization creation + const handleCreateOrganization = async () => { + if (!orgName || !orgSlug || !orgDomain || !domainVerified) { + toast.error('Please fill in all fields and verify your domain'); + return; + } + setCreatingOrg(true); + try { + await authClient.organization.create({ + name: orgName, + slug: orgSlug, + }); + toast.success(`Organization "${orgName}" created successfully!`); + setOrgName(''); + setOrgSlug(''); + setOrgDomain(''); + setDomainVerificationToken(null); + setDomainVerified(false); + setVerifyMsg(null); + setVerificationToken(null); + // Refresh organizations list + const orgs = await authClient.organization.list(); + setOrganizations(orgs.data || []); + } catch (error: any) { + toast.error(`Failed to create organization: ${error.message}`); + } finally { + setCreatingOrg(false); + } + }; + + // Check domain verification and slug availability + const handleAddDomain = async () => { + if (!orgName || !orgSlug || !orgDomain) { + toast.error('Please fill in organization name, slug, and domain'); + return; + } + setVerifying(true); + setVerifyMsg(null); + + try { + // Check if slug is available + const orgs = await authClient.organization.list(); + const slugExists = orgs.data?.some((org) => org.slug === orgSlug); + if (slugExists) { + toast.error('Organization slug already exists. Please choose a different one.'); + setVerifying(false); + return; + } + + // Check domain verification directly + const result = await verifyDomainMutation.mutateAsync({ + domain: orgDomain, + verificationToken: verificationToken ?? undefined, + }); + + // Store the verification token for reuse + if (result.verificationToken) { + setVerificationToken(result.verificationToken); + } + + if (result.verified) { + setDomainVerified(true); + setVerifyMsg('Domain verified! You can now create your organization.'); + } else { + setVerifyMsg( + result.message || 'Domain verification failed. Please add the TXT record to your DNS.', + ); + } + } catch (error: any) { + toast.error(`Failed to verify domain: ${error.message}`); + } finally { + setVerifying(false); + } + }; + + // DEBUG BYPASS VERIFICATION + const handleDebugBypass = () => { + setDomainVerified(true); + toast.success('DEBUG: Domain verification bypassed!'); + }; + + return ( + + + + + Create Organization + + + Create a new organization. You must verify your domain before proceeding. + + + +
+
+ + setOrgName(e.target.value)} + placeholder="My Organization" + /> +
+
+ + setOrgSlug(e.target.value)} + placeholder="my-org" + /> +
+
+ + setOrgDomain(e.target.value)} + placeholder="example.com" + /> + +
+
+ {verifyMsg && ( +
+
{verifyMsg}
+ {verificationToken && !domainVerified && ( +
+
Add this TXT record to your DNS:
+ + zero-verification={verificationToken} + + +
+ )} + +
+ )} + +
+
+ ); +} diff --git a/apps/mail/components/organization/invite-member.tsx b/apps/mail/components/organization/invite-member.tsx new file mode 100644 index 000000000..360a48dd9 --- /dev/null +++ b/apps/mail/components/organization/invite-member.tsx @@ -0,0 +1,99 @@ +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Loader2, Mail, UserPlus } from 'lucide-react'; +import { authClient } from '@/lib/auth-client'; +import { Label } from '@/components/ui/label'; +import { Input } from '@/components/ui/input'; +import { Button } from '../ui/button'; +import { useState } from 'react'; +import { toast } from 'sonner'; + +type Role = 'member' | 'admin' | 'owner'; + +export default function InviteMember({ orgId, orgName }: { orgId: string; orgName: string }) { + const [inviteEmail, setInviteEmail] = useState(''); + const [inviteRole, setInviteRole] = useState('member'); + const [loading, setLoading] = useState(false); + + // Test member invitation + const handleInviteMember = async () => { + if (!inviteEmail || !orgId || !orgName) { + toast.error('Please enter an email and select an organization'); + return; + } + + setLoading(true); + try { + await authClient.organization.inviteMember({ + email: inviteEmail, + role: inviteRole as Role, + organizationId: orgId, + }); + + toast.success(`Invitation sent to ${inviteEmail}!`); + setInviteEmail(''); + } catch (error: any) { + toast.error(`Failed to send invitation: ${error.message}`); + } finally { + setLoading(false); + } + }; + + return ( + + + + + Invite Member + + Invite a new member to "{orgName}" + + +
+
+ + setInviteEmail(e.target.value)} + placeholder="member@example.com" + /> +
+
+ + +
+
+ +
+
+ ); +} diff --git a/apps/mail/components/organization/my-organizations.tsx b/apps/mail/components/organization/my-organizations.tsx new file mode 100644 index 000000000..079447d1e --- /dev/null +++ b/apps/mail/components/organization/my-organizations.tsx @@ -0,0 +1,120 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import { useTRPC } from '@/providers/query-provider'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '../ui/button'; +import { Users } from 'lucide-react'; +import { toast } from 'sonner'; + +export default function MyOrganizations({ + organizations, + activeOrg, + handleSetActiveOrg, +}: { + organizations: any[]; + activeOrg: any; + handleSetActiveOrg: (org: any) => void; +}) { + const trpc = useTRPC(); + + const { data: defaultOrganizationId, refetch: refetchDefaultOrganizationId } = useQuery({ + ...trpc.organization.getUsersDefaultOrganizationId.queryOptions(), + }); + + const setDefaultOrganizationMutation = useMutation( + trpc.organization.setDefaultOrganization.mutationOptions({ + onSuccess: () => { + toast.success('Default organization set successfully'); + }, + onError: (error) => { + toast.error(`Failed to set default organization: ${error.message}`); + }, + }), + ); + + const handleSetDefaultOrganization = async (org: any) => { + await setDefaultOrganizationMutation.mutateAsync({ + organizationId: org.id, + }); + refetchDefaultOrganizationId(); + }; + + console.dir(defaultOrganizationId); + + return ( + + + + + Your Organizations + + Select an organization to manage members + + +
+ {organizations.map((org) => ( +
+
+ {org.logo && ( + {`${org.name} { + (e.currentTarget as HTMLImageElement).style.display = 'none'; + }} + /> + )} +
+

{org.name}

+

@{org.slug}

+
+ +
+ {activeOrg?.id !== org.id && ( + + )} + {defaultOrganizationId?.defaultOrganizationId !== org.id && ( + + )} +
+
+
+ {activeOrg?.id === org.id && Active} + {defaultOrganizationId?.defaultOrganizationId === org.id && ( + Default + )} +
+ {/* {org.member?.role || 'Unknown'} */} +
+ ))} +
+
+
+ ); +} diff --git a/apps/mail/components/organization/pending-invites.tsx b/apps/mail/components/organization/pending-invites.tsx new file mode 100644 index 000000000..66256738c --- /dev/null +++ b/apps/mail/components/organization/pending-invites.tsx @@ -0,0 +1,84 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Loader2, Mail } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { toast } from 'sonner'; + +export default function PendingInvites({ orgId, orgName }: { orgId: string; orgName: string }) { + // Invitations state + const [invites, setInvites] = useState([]); + const [loadingInvites, setLoadingInvites] = useState(false); + // Fetch pending invitations - TODO: Convert to TRPC when invitation router is available + async function fetchInvites() { + if (!orgId) return; + setLoadingInvites(true); + try { + const res = await fetch( + `${import.meta.env.VITE_PUBLIC_BACKEND_URL}/api/invitations?organizationId=${orgId}&status=pending`, + { credentials: 'include' }, + ); + const data = (await res.json()) as { invitations: any[] }; + setInvites(data.invitations || []); + } catch (error) { + console.error('Failed to fetch invitations:', error); + } finally { + setLoadingInvites(false); + } + } + + // Cancel an invitation - TODO: Convert to TRPC when invitation router is available + async function cancelInvite(inviteId: string) { + if (!inviteId) return; + try { + await fetch(`${import.meta.env.VITE_PUBLIC_BACKEND_URL}/api/invitations/${inviteId}`, { + method: 'DELETE', + credentials: 'include', + }); + toast.success('Invitation cancelled'); + fetchInvites(); + } catch (error: any) { + toast.error(`Failed to cancel invite: ${error.message}`); + } + } + + // Fetch invitations when org changes + useEffect(() => { + if (orgId) fetchInvites(); + }, [orgId]); + + return ( + + + + + Pending Invitations + + Manage outgoing invitations for "{orgName}" + + + {loadingInvites ? ( +
+ +
+ ) : invites.length > 0 ? ( + invites.map((invite) => ( +
+
+

{invite.email}

+

{invite.role}

+
+ +
+ )) + ) : ( +

No pending invitations.

+ )} +
+
+ ); +} diff --git a/apps/server/src/db/migrations/0042_wealthy_earthquake.sql b/apps/server/src/db/migrations/0042_wealthy_earthquake.sql new file mode 100644 index 000000000..85ba75b28 --- /dev/null +++ b/apps/server/src/db/migrations/0042_wealthy_earthquake.sql @@ -0,0 +1,2 @@ +ALTER TABLE "mail0_user" ADD COLUMN "active_organization_id" text;--> statement-breakpoint +ALTER TABLE "mail0_user" ADD CONSTRAINT "mail0_user_active_organization_id_mail0_organization_id_fk" FOREIGN KEY ("active_organization_id") REFERENCES "public"."mail0_organization"("id") ON DELETE no action ON UPDATE no action; \ No newline at end of file diff --git a/apps/server/src/db/migrations/meta/0042_snapshot.json b/apps/server/src/db/migrations/meta/0042_snapshot.json new file mode 100644 index 000000000..227b10cbd --- /dev/null +++ b/apps/server/src/db/migrations/meta/0042_snapshot.json @@ -0,0 +1,2124 @@ +{ + "id": "ad7cf597-86f3-425a-bcf8-4f51de2386a4", + "prevId": "f0531169-7ecf-496b-a59a-b19128986fe3", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.mail0_account": { + "name": "mail0_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_provider_user_id_idx": { + "name": "account_provider_user_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_expires_at_idx": { + "name": "account_expires_at_idx", + "columns": [ + { + "expression": "access_token_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mail0_account_user_id_mail0_user_id_fk": { + "name": "mail0_account_user_id_mail0_user_id_fk", + "tableFrom": "mail0_account", + "tableTo": "mail0_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_connection": { + "name": "mail0_connection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "picture": { + "name": "picture", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "connection_user_id_idx": { + "name": "connection_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_expires_at_idx": { + "name": "connection_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_provider_id_idx": { + "name": "connection_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mail0_connection_user_id_mail0_user_id_fk": { + "name": "mail0_connection_user_id_mail0_user_id_fk", + "tableFrom": "mail0_connection", + "tableTo": "mail0_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mail0_connection_user_id_email_unique": { + "name": "mail0_connection_user_id_email_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_early_access": { + "name": "mail0_early_access", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_early_access": { + "name": "is_early_access", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "has_used_ticket": { + "name": "has_used_ticket", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + } + }, + "indexes": { + "early_access_is_early_access_idx": { + "name": "early_access_is_early_access_idx", + "columns": [ + { + "expression": "is_early_access", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mail0_early_access_email_unique": { + "name": "mail0_early_access_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_invitation": { + "name": "mail0_invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviterId": { + "name": "inviterId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "teamId": { + "name": "teamId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mail0_invitation_inviterId_mail0_user_id_fk": { + "name": "mail0_invitation_inviterId_mail0_user_id_fk", + "tableFrom": "mail0_invitation", + "tableTo": "mail0_user", + "columnsFrom": [ + "inviterId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "mail0_invitation_organizationId_mail0_organization_id_fk": { + "name": "mail0_invitation_organizationId_mail0_organization_id_fk", + "tableFrom": "mail0_invitation", + "tableTo": "mail0_organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_jwks": { + "name": "mail0_jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "jwks_created_at_idx": { + "name": "jwks_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_member": { + "name": "mail0_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "teamId": { + "name": "teamId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mail0_member_userId_mail0_user_id_fk": { + "name": "mail0_member_userId_mail0_user_id_fk", + "tableFrom": "mail0_member", + "tableTo": "mail0_user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "mail0_member_organizationId_mail0_organization_id_fk": { + "name": "mail0_member_organizationId_mail0_organization_id_fk", + "tableFrom": "mail0_member", + "tableTo": "mail0_organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "mail0_member_teamId_mail0_team_id_fk": { + "name": "mail0_member_teamId_mail0_team_id_fk", + "tableFrom": "mail0_member", + "tableTo": "mail0_team", + "columnsFrom": [ + "teamId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mail0_member_userId_organizationId_unique": { + "name": "mail0_member_userId_organizationId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId", + "organizationId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_note": { + "name": "mail0_note", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "is_pinned": { + "name": "is_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "note_user_id_idx": { + "name": "note_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "note_thread_id_idx": { + "name": "note_thread_id_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "note_user_thread_idx": { + "name": "note_user_thread_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "note_is_pinned_idx": { + "name": "note_is_pinned_idx", + "columns": [ + { + "expression": "is_pinned", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mail0_note_user_id_mail0_user_id_fk": { + "name": "mail0_note_user_id_mail0_user_id_fk", + "tableFrom": "mail0_note", + "tableTo": "mail0_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_oauth_access_token": { + "name": "mail0_oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_access_token_user_id_idx": { + "name": "oauth_access_token_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_client_id_idx": { + "name": "oauth_access_token_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_expires_at_idx": { + "name": "oauth_access_token_expires_at_idx", + "columns": [ + { + "expression": "access_token_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mail0_oauth_access_token_access_token_unique": { + "name": "mail0_oauth_access_token_access_token_unique", + "nullsNotDistinct": false, + "columns": [ + "access_token" + ] + }, + "mail0_oauth_access_token_refresh_token_unique": { + "name": "mail0_oauth_access_token_refresh_token_unique", + "nullsNotDistinct": false, + "columns": [ + "refresh_token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_oauth_application": { + "name": "mail0_oauth_application", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_u_r_ls": { + "name": "redirect_u_r_ls", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_application_user_id_idx": { + "name": "oauth_application_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_application_disabled_idx": { + "name": "oauth_application_disabled_idx", + "columns": [ + { + "expression": "disabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mail0_oauth_application_client_id_unique": { + "name": "mail0_oauth_application_client_id_unique", + "nullsNotDistinct": false, + "columns": [ + "client_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_oauth_consent": { + "name": "mail0_oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "consent_given": { + "name": "consent_given", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_consent_user_id_idx": { + "name": "oauth_consent_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_consent_client_id_idx": { + "name": "oauth_consent_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_consent_given_idx": { + "name": "oauth_consent_given_idx", + "columns": [ + { + "expression": "consent_given", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_organization": { + "name": "mail0_organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mail0_organization_slug_unique": { + "name": "mail0_organization_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_organization_connection": { + "name": "mail0_organization_connection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connectionId": { + "name": "connectionId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "alias": { + "name": "alias", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "mail0_organization_connection_organizationId_mail0_organization_id_fk": { + "name": "mail0_organization_connection_organizationId_mail0_organization_id_fk", + "tableFrom": "mail0_organization_connection", + "tableTo": "mail0_organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mail0_organization_connection_connectionId_mail0_connection_id_fk": { + "name": "mail0_organization_connection_connectionId_mail0_connection_id_fk", + "tableFrom": "mail0_organization_connection", + "tableTo": "mail0_connection", + "columnsFrom": [ + "connectionId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_organization_domain": { + "name": "mail0_organization_domain", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verificationToken": { + "name": "verificationToken", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mail0_organization_domain_organizationId_mail0_organization_id_fk": { + "name": "mail0_organization_domain_organizationId_mail0_organization_id_fk", + "tableFrom": "mail0_organization_domain", + "tableTo": "mail0_organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_session": { + "name": "mail0_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_expires_at_idx": { + "name": "session_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mail0_session_user_id_mail0_user_id_fk": { + "name": "mail0_session_user_id_mail0_user_id_fk", + "tableFrom": "mail0_session", + "tableTo": "mail0_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mail0_session_active_organization_id_mail0_organization_id_fk": { + "name": "mail0_session_active_organization_id_mail0_organization_id_fk", + "tableFrom": "mail0_session", + "tableTo": "mail0_organization", + "columnsFrom": [ + "active_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mail0_session_token_unique": { + "name": "mail0_session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_summary": { + "name": "mail0_summary", + "schema": "", + "columns": { + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "saved": { + "name": "saved", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "suggested_reply": { + "name": "suggested_reply", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "summary_connection_id_idx": { + "name": "summary_connection_id_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_connection_id_saved_idx": { + "name": "summary_connection_id_saved_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "saved", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_saved_idx": { + "name": "summary_saved_idx", + "columns": [ + { + "expression": "saved", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mail0_summary_connection_id_mail0_connection_id_fk": { + "name": "mail0_summary_connection_id_mail0_connection_id_fk", + "tableFrom": "mail0_summary", + "tableTo": "mail0_connection", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_team": { + "name": "mail0_team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organizationId": { + "name": "organizationId", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mail0_team_organizationId_mail0_organization_id_fk": { + "name": "mail0_team_organizationId_mail0_organization_id_fk", + "tableFrom": "mail0_team", + "tableTo": "mail0_organization", + "columnsFrom": [ + "organizationId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_user": { + "name": "mail0_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "default_connection_id": { + "name": "default_connection_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_organization_id": { + "name": "default_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "custom_prompt": { + "name": "custom_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phone_number_verified": { + "name": "phone_number_verified", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "mail0_user_default_organization_id_mail0_organization_id_fk": { + "name": "mail0_user_default_organization_id_mail0_organization_id_fk", + "tableFrom": "mail0_user", + "tableTo": "mail0_organization", + "columnsFrom": [ + "default_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "mail0_user_active_organization_id_mail0_organization_id_fk": { + "name": "mail0_user_active_organization_id_mail0_organization_id_fk", + "tableFrom": "mail0_user", + "tableTo": "mail0_organization", + "columnsFrom": [ + "active_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mail0_user_email_unique": { + "name": "mail0_user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "mail0_user_phone_number_unique": { + "name": "mail0_user_phone_number_unique", + "nullsNotDistinct": false, + "columns": [ + "phone_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_user_hotkeys": { + "name": "mail0_user_hotkeys", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "shortcuts": { + "name": "shortcuts", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "user_hotkeys_shortcuts_idx": { + "name": "user_hotkeys_shortcuts_idx", + "columns": [ + { + "expression": "shortcuts", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mail0_user_hotkeys_user_id_mail0_user_id_fk": { + "name": "mail0_user_hotkeys_user_id_mail0_user_id_fk", + "tableFrom": "mail0_user_hotkeys", + "tableTo": "mail0_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_user_settings": { + "name": "mail0_user_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"language\":\"en\",\"timezone\":\"UTC\",\"dynamicContent\":false,\"externalImages\":true,\"customPrompt\":\"\",\"trustedSenders\":[],\"isOnboarded\":false,\"colorTheme\":\"system\",\"zeroSignature\":true,\"autoRead\":true,\"defaultEmailAlias\":\"\",\"categories\":[{\"id\":\"Important\",\"name\":\"Important\",\"searchValue\":\"is:important NOT is:sent NOT is:draft\",\"order\":0,\"icon\":\"Lightning\",\"isDefault\":false},{\"id\":\"All Mail\",\"name\":\"All Mail\",\"searchValue\":\"NOT is:draft (is:inbox OR (is:sent AND to:me))\",\"order\":1,\"icon\":\"Mail\",\"isDefault\":true},{\"id\":\"Personal\",\"name\":\"Personal\",\"searchValue\":\"is:personal NOT is:sent NOT is:draft\",\"order\":2,\"icon\":\"User\",\"isDefault\":false},{\"id\":\"Promotions\",\"name\":\"Promotions\",\"searchValue\":\"is:promotions NOT is:sent NOT is:draft\",\"order\":3,\"icon\":\"Tag\",\"isDefault\":false},{\"id\":\"Updates\",\"name\":\"Updates\",\"searchValue\":\"is:updates NOT is:sent NOT is:draft\",\"order\":4,\"icon\":\"Bell\",\"isDefault\":false},{\"id\":\"Unread\",\"name\":\"Unread\",\"searchValue\":\"is:unread NOT is:sent NOT is:draft\",\"order\":5,\"icon\":\"ScanEye\",\"isDefault\":false}],\"imageCompression\":\"medium\"}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "user_settings_settings_idx": { + "name": "user_settings_settings_idx", + "columns": [ + { + "expression": "settings", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mail0_user_settings_user_id_mail0_user_id_fk": { + "name": "mail0_user_settings_user_id_mail0_user_id_fk", + "tableFrom": "mail0_user_settings", + "tableTo": "mail0_user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mail0_user_settings_user_id_unique": { + "name": "mail0_user_settings_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_verification": { + "name": "mail0_verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mail0_writing_style_matrix": { + "name": "mail0_writing_style_matrix", + "schema": "", + "columns": { + "connectionId": { + "name": "connectionId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "numMessages": { + "name": "numMessages", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "style": { + "name": "style", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "writing_style_matrix_style_idx": { + "name": "writing_style_matrix_style_idx", + "columns": [ + { + "expression": "style", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mail0_writing_style_matrix_connectionId_mail0_connection_id_fk": { + "name": "mail0_writing_style_matrix_connectionId_mail0_connection_id_fk", + "tableFrom": "mail0_writing_style_matrix", + "tableTo": "mail0_connection", + "columnsFrom": [ + "connectionId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "mail0_writing_style_matrix_connectionId_pk": { + "name": "mail0_writing_style_matrix_connectionId_pk", + "columns": [ + "connectionId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.role": { + "name": "role", + "schema": "public", + "values": [ + "owner", + "admin", + "member" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/server/src/db/migrations/meta/_journal.json b/apps/server/src/db/migrations/meta/_journal.json index 4f7657e37..e256fcabe 100644 --- a/apps/server/src/db/migrations/meta/_journal.json +++ b/apps/server/src/db/migrations/meta/_journal.json @@ -295,6 +295,13 @@ "when": 1752527297548, "tag": "0041_acoustic_nick_fury", "breakpoints": true + }, + { + "idx": 42, + "version": "7", + "when": 1752765047458, + "tag": "0042_wealthy_earthquake", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/apps/server/src/db/schema.ts b/apps/server/src/db/schema.ts index 5024b70fb..538968dc8 100644 --- a/apps/server/src/db/schema.ts +++ b/apps/server/src/db/schema.ts @@ -24,6 +24,7 @@ export const user = createTable('user', { updatedAt: timestamp('updated_at').notNull(), defaultConnectionId: text('default_connection_id'), defaultOrganizationId: text('default_organization_id').references(() => organization.id), + activeOrganizationId: text('active_organization_id').references(() => organization.id), customPrompt: text('custom_prompt'), phoneNumber: text('phone_number').unique(), phoneNumberVerified: boolean('phone_number_verified'), diff --git a/apps/server/src/trpc/routes/organization.ts b/apps/server/src/trpc/routes/organization.ts index 0a0a90eaa..36d66565f 100644 --- a/apps/server/src/trpc/routes/organization.ts +++ b/apps/server/src/trpc/routes/organization.ts @@ -785,4 +785,55 @@ export const organizationRouter = router({ await conn.end(); } }), + getUsersDefaultOrganizationId: privateProcedure.query(async ({ ctx }) => { + const { db, conn } = createDb(ctx.c.env.HYPERDRIVE.connectionString); + try { + const [data] = await db.select().from(user).where(eq(user.id, ctx.sessionUser.id)); + return { defaultOrganizationId: data?.defaultOrganizationId } as const; + } finally { + await conn.end(); + } + }), + setDefaultOrganization: privateProcedure + .input(z.object({ organizationId: z.string() })) + .mutation(async ({ input, ctx }) => { + const { organizationId } = input; + const { sessionUser } = ctx; + const { db, conn } = createDb(ctx.c.env.HYPERDRIVE.connectionString); + try { + await db + .update(user) + .set({ defaultOrganizationId: organizationId }) + .where(eq(user.id, sessionUser.id)); + return { success: true } as const; + } finally { + await conn.end(); + } + }), + + getUsersActiveOrganizationId: privateProcedure.query(async ({ ctx }) => { + const { db, conn } = createDb(ctx.c.env.HYPERDRIVE.connectionString); + try { + const [data] = await db.select().from(user).where(eq(user.id, ctx.sessionUser.id)); + return { activeOrganizationId: data?.activeOrganizationId } as const; + } finally { + await conn.end(); + } + }), + setActiveOrganization: privateProcedure + .input(z.object({ organizationId: z.string() })) + .mutation(async ({ input, ctx }) => { + const { organizationId } = input; + const { sessionUser } = ctx; + const { db, conn } = createDb(ctx.c.env.HYPERDRIVE.connectionString); + try { + await db + .update(user) + .set({ activeOrganizationId: organizationId }) + .where(eq(user.id, sessionUser.id)); + return { success: true } as const; + } finally { + await conn.end(); + } + }), });